`
xyheqhd888
  • 浏览: 403908 次
  • 性别: Icon_minigender_1
  • 来自: 秦皇岛
社区版块
存档分类
最新评论

Flyweight(享元)模式

阅读更多

Flyweight模式可实现客户代码之间的对象共享,创建共享对象的职责,这一点普通对象是不具备的。一般的对象不必关心共享职责,任何时刻最多只能有一个客户代码引用它,其他时刻可以是其他客户代码引用。如果多个客户代码引用同一个对象,那么当某个客户代码修改了该对象的状态时,该对象是不会通知其他客户代码的。然而,有时候,我们需要让多个客户代码共享访问同一个对象。

    当我们必须管理成千上万个小型对象的时候,例如在线电子图书中的字符对象,我们需要让多个客户代码共享一个对象。在这种情况下,为了提高应用程序的性能,需要考虑这些细粒度的对象在多个客户代码之间的安全共享访问问题。对于在线电子图书来说,一本书只需要一个A对象,但是当出现不同的A对象时,则需要我们采取某种方法对之建模。

 

   Flyweight模式的主要意图是:通过共享来支持大量的细粒度对象的使用效率

 

1. 不变性

  Flyweight模式可让多个客户安全共享对有限数量对象的访问。为实现这个目标,我们必须考虑如果某个客户代码改变了共享对象的状态,这将会影响共享该对象的其他所有客户代码。在最普通的情况下,该对象仅被一个单独的客户代码访问,自然不存在任何问题。当多个客户代码共享访问一个对象的时候,如何避免客户代码之间的相互影响是个值得关注的问题,这个问题最简单和最常见的解决办法,便是限制客户代码调用任何可能引起共享对象状态变化的方法。在创建对象时,可以将这个对象定义为immutable类型这样该对象就不会被改变。Java语言中不可变对象是String类对象。一旦创建了一个String对象,任何客户代码都无法改变它的字符

 

突破题:你是否认同Java语言设计者将String对象定义为不可变的做法,并请给出你的理由。

答:赞成方观点,主要是考虑到实际应用中,字符串通常被多个用户共享。如果字符串能够被修改,那么一个用户对字符串不经意的改动就会影响其他用户。这种问题在实际应用中经常会出现,它的根结在于字符串可能会被修改。例如,当一个方法返回一个Customer对象的客户名字符串之后,它仍然保留了对该字符串的引用。如果字符串可以被修改,那么当用户在散列表中将该字符串变成全大写的形式,Customer对象的名字也会随之改变。在Java语言中,你可以把某字符串全部大写,但必须使用新对象,而不是仅仅修改原来的对象。字符串的不变性有利于多个用户安全地共享该字符串。更进一步说,字符串的不可变性也可以防止系统出现安全危机。

     反对方观点:将字符串设置为不变,这样做的确能够避免我们犯某些错误,但是其负面影响也很大。首先,即使在确实需要修改的场合,开发人员也无法修改字符串。第二,在一种计算机语言中加入特殊的规则,将会使该语言难以学习和使用。Java语言和Smalltalk语言功能一样强大,但是前者就比后者难学多了。最后,任何语言都无法避免使用者犯错误。如果一种计算机语言简单易学,使用者就能够更加深入地掌握它,这样使用者就会有更多的时间来研究如何构建和使用该语言的测试架构。

 

     如果有大量的类似对象需要管理,也许需要共享这些对象;不过,它们可能不是不可变的。在这种情况下,我们必须先将对象的不可变部分提取出来,首先共享不变的部分。

 

2.提取享元中不可变的部分

  对于文档而言,字符普通存在;而对于Oozinoz公司而言,化学品到处都是。该公司的采购、工程、生产、安全等部门都在监控这成千上万的化学品在整个工厂的流动情况。经过建模,这些化学品都被描述为Substance类的实例。如下图所示:

化学品被建模为Substance类的实例

Substance类提供众多可以访问其属性的方法,另外还提供getMoles()方法用于返回该化学品的摩尔数---即分子数量。Substance对象表示化学品的摩尔数。Oozinoz公司使用Mixture类来模拟化学品的组成。黑火药由硝石粉、硫磺、碳粉等组成。这些都是Substance类的实例。

  考虑到Oozinoz公司的化学原料会越来越多,因而我们决定使用Flyweight模式来对这些化学品建模,减少Substance对象的数量。为把Subsatnce对象模拟为享元,首先需要区分该类的可变部分和不可变部分。假设你决定重构Substance类,把其中不可变部分提出放入Chemical类中。

 

突破题:描述出被重构的Substance2类和新的不可变类,Chemical类:

 

该图显示Substance类的不可变属性被提取出来放到单独的类(Chemical)中

你可以把Subsance对象的不可变部分---包括化学制品的名称、化学符号以及原子量(或分子量)---放入Chemical类中。如上图所示。

  Substance2类保持对Chemical对象的引用。结果Subsance2类仍旧具有与早期版本Subsance类相同的属性。从内部来讲,这些附属属性依赖于Chemical类,就像Substance2类方法演示的那样:

  
public double getAtomicWeight()
{
    return chemical.getAtomicWeight();
}

public double getGrams()
{
    return grams;
}

public douible getMoles()
{
    return grams/getAtomicWeight();
}

 

3. 共享享元:

   将对象的不可变部分提取出来仅是应用Flyweight模式前面的一半工作。后一半工作是创建一个享元工厂,用于实例化享元和组织共享享元的客户代码。另外,我们必须保证各个客户代码都将使用享元工厂创建享元实例,而不能自己创建。

   为了把Chemical对象变成享元,我们创建了一个用于生成化学品享元的工厂ChemicalFactory类,该类包含一个静态方法,利用该方法可以返回指定名称的化学品享元的实例。在该工厂类初始化的时候,我们创建出各种已知常用的化学品享元实例,并将它们存放在一个散列表中。下图给出了ChemicalFactory类的设计。

package com.oozinoz.chemical;
import java.util.*;

public class ChemicalFactory
{
    private static Map chemicals = new HashMap();

    static {
        chemicals.put("carbon",new Chemical("Carbon","C",12));
        chemicals.put("sulfur",new Chemical("Sulfur","S",32));
        chemicals.put("saltpeter",new Chemical("Saltpeter","KN03",101));
        //...
    }

    public static Chemical getChemical(String name)
    {
        return (Chemical) chemicals.get(name.toLowerCase());
    }
}


 

ChemicalFactory类是返回Chemical对象的享元工厂类

  ChemicalFactory类的代码使用静态的初始化方法将Chemical对象存放在散列表中:

 在创建了享元化学品的工厂类之后,我们还必须采取某些措施来保证其他开发人员一定会用享元工厂类来创建享元,而不能直接实例化Chemical享元类。有一个简单的做法就是借助Chemical类的可见性。

 

突破题:请问如何通过设置Chemical类的可见性来防止其他开发者直接实例化Chemical类?

答:一种方式是把Chemical类构造器定义为内部方法。这种做法可以防止ChemicalFactory类实例化Chemical类。

     为避免开发人员直接实例化Chemical类,可以将Chemical和ChemicalFactory放在同一个包中,并将Chemical类的构造器声明为私有的。

 

  然而,仅通过设置可见性修饰符,还无法完全控制享元的实例化。如果要确保ChemicalFactory类是唯一可以创建Chemical类对象的类,那么可以将Chemical类定义为ChemicalFactory的内部类(使用inner修饰符来定义)。

 为访问嵌套的类型,客户代码必须使用下面的表达式指定封装类型。

ChemicalFactory.Chemical c = ChemicalFactory.getChemical("saltpeter");

 使用如下办法可以简化对被嵌套类的使用:将Chemical定义为接口,将嵌套类的名称定义为ChemicalImpl。Chemical接口可以指定三个访问方法,如下所示:

package com.oozinoz.chemical2;
public interface Chemical
{
    String getName();
    String getSymbol();
    double getAtomicWeight();
}

 客户代码不直接引用内部类,所以可设置为私有,这样就保证了只有ChmicalFactory2类可访问它。

 

突破题:完成下面的ChemicalFactory2.java类的代码:

    使用嵌套类虽然更加复杂,但是能够彻底确保只有ChemicalFactory2类可以实例化新的享元。 

package com.oozinoz.chemical2;
import java.util.*;

public class ChemicalFactory2
{
	private static Map chemicals = new HashMap();

	class ChemicalImpl implements Chemical
	{
		private String name;
		private String symbol;
		private double atomicWeight;

		ChemicalImpl(
				String name,
				String symbol,
				double atomicWeight){
			this.name = name;
			this.symbol = symbol;
			this.atomicWeight = atomicWeight;
		}

		public String getName()
		{
			return name;
		}

		public String getSymbol()
		{
			return symbol;
		}

		public double getAtomicWeight()
		{
			return atomicWeight;
		}

		public String toString()
		{
			return name+"("+symbol+")[" + atomicWeight + "]";
		}
	}

	static{
		ChemicalFactory2 factory = new ChemicalFactory2();
		chemicals.put("carbon",factory.new ChemicalImpl("Carbon","C",12));
		chemicals.put("sulfur",factory.new ChemicalImpl("Sulfur","S",32));
		chemicals.put("saltpeter",factory.new ChemicalImpl("Saltpeter","KN03",101));
		//...
	}

	public static Chemical getChemical(String name)
	{
		return (Chemical) chemicals.get(name.toLowerCase());
	}
}

上述代码可解决三个问题:

1. ChemicalImpl嵌套类应该是私有的,保证只有ChemicalFactory2类可以使用该类。请注意嵌套类的访问范围必须是包范围,或者可以公开访问,这样包含的类可以实例化被嵌套的类。即使把构造器定义为公开访问的,如果嵌套类本身被标识为私有,则没有其他类可以使用这个构造器。

2.ChemicalFactory2构造器使用静态实例化方法,以确保本类只能创建一次化学药品列表。

3.getChemical()方法应该在类的散列表中根据名称来查找化学药品。范例代码使用小写的药品名称来存储和查找化学药品。

 

4.小结

  字符、化学品以及边界等对象往往会大量出现,在对它们进行建模的时候,可以使用Flyweight模式来管理对它们的共享访问。享元对象的属性必须是不可变的。为了满足这一特性,我们可以将这些对象中不可改变的部分提取出来构建成享元类。另外,为了确保享元对象能够被共享,我们还必须提供一个用于创建和查找享元对象的工厂类,并且保证客户只能使用该类来创建享元对象。设置享元类的可见性修饰符可以在某些方面帮助我们控制其他开发者对享元对象的访问,但是内部类做得更好,能够确保享元类只能被享元工厂类访问。在确保客户能够适当地使用享元工厂之后,我们就可以出色地管理对大量细粒度对象的共享访问。

  • 大小: 3.2 KB
  • 大小: 4.9 KB
  • 大小: 2.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics