设计模式之创建型模式

Author Avatar
罗炜光 4月 10, 2016
  • 在其它设备中阅读本文章

简单工厂(Simple Factory)

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式

一般性结构

  • 工厂角色:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象,它往往由一个具体的Java类实现
  • 抽象产品:担任这个角色的类是由工厂模式所创建的对象的父类,或它们共同拥有的接口。抽象产品角色可以用一个Java接口或者Java抽象类实现
  • 具体产品:工厂模式所创建的任何对象都是这个角色的实例,具体产品角色由一个具体Java类实现。

优点:

  • 将创建对象与使用对象的职责分开

缺点:

  • 因为工厂类集中了所有产品的创建逻辑,那么工厂类出现问题,会导致所有需要工厂类创建对象的调用都会出现问题。
  • 由于使用静态方法,所以无法被子类继承
  • 每次扩展产品类时,都必须对工厂方法进行修改

适用场景

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心

例子:

public class Factory
{
    public static Product FactoryMethod(String str)
    {
        if ("Apple".equals(str))
        {
            return new Aplle();
        } else if ("Pear".equals(str))
        {
            return new Pear();
        } else
        {
            return null;
        }
    }
}

interface Product
{
    void eat();
}

class Aplle implements Product
{
    public void eat()
    {
        System.out.println("吃苹果");
    }
}

class Pear implements Product
{
    public void eat()
    {
        System.out.println("吃梨");
    }

}

class Test
{
    public static void main(String[] args)
    {
        Product product = Factory.FactoryMethod("Apple");
        product.eat();
    }
}

工厂方法(Factory Method)

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

一般性结构

  • 抽象工厂:担任这个角色的是工厂方法模式的核心,它是与应用程序无关的,任何在模式中创建对象的工厂类必须实现这个接口。在实际的系统中,这个角色也常常使用抽象Java类实现。
  • 具体工厂:担任这个角色的是实现了抽象工厂接口的具体Java类。具体工厂角色含有与应用密切相关的逻辑,并且受到应用程序的调用以创建产品对象。
  • 抽象产品:工厂方法模式所创建的对象的超类型,也就是产品对象的共有父类或共同拥有的接口。在实际的系统中。常常使用抽象Java类实现。
  • 具体产品:这个角色实现了抽象产品角色所声明的接口。工厂方法模式所创建的每一个对象都是某个具体产品角色的实例。

优点:

  • 将创建对象与使用对象的职责分开
  • 无需修改客户端的逻辑代码就可以添加具体的产品

缺点:

  • 每次扩展产品类时,都必须添加新的具体工厂类
  • 增加了系统的抽象性与理解难度

适用场景

  • 客户端不知道它所需要的对象的类
  • 抽象工厂类通过其子类来指定创建哪个对象

例子

public class Test
{
    private static FruitCreator mFruitCreator;

    public static void main(String[] args)
    {
        mFruitCreator = new AppleCreator();
        mFruitCreator.factory().eat();
    }
}

interface FruitCreator
{
    public Fruit factory();
}

interface Fruit
{
    public void eat();
}

class AppleCreator implements FruitCreator
{
    @Override
    public Fruit factory()
    {
        return new Apple();
    }
}

class PearCreator implements FruitCreator
{
    @Override
    public Fruit factory()
    {
        return new Pear();
    }
}

class Apple implements Fruit
{
    @Override
    public void eat()
    {
        System.out.println("吃苹果");
    }
}

class Pear implements Fruit
{
    @Override
    public void eat()
    {
        System.out.println("吃梨");
    }
}

抽象工厂(Abstract Factory)

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式

一般性结构

  • 抽象工厂:担任这个角色的是工厂方法模式的核心,它是与应用系统的商业逻辑无关的。通常使用Java接口或者抽象Java类实现,而所有的具体工厂类必须实现这个Java接口或继承这个抽象Java类
  • 具体工厂:这个角色直接在客户端的调用下创建产品的实例的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。通常使用具体Java类实现这个角色。
  • 抽象产品:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。通常使用Java接口或抽象Java类实现。
  • 具体产品:抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例,这个客户端最终需要的东西,其内部一定充满了应用系统的商业逻辑。通常使用具体Java类实现这个角色。

优点:

  • 将创建对象与使用对象的职责分开
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
  • 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”

缺点:

  • 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

适用场景

  • 客户端不知道它所需要的对象的类
  • 系统中有多于一个的产品族,而每次只使用其中某一产品族
  • 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

例子:


public class Test
{
    public static void main(String[] args)
    {
        SkinFactory mSkinFactory= new RedSkinFactory();
        mSkinFactory.createButton().paint();
        mSkinFactory.createTextView().paint();
    }
}

interface SkinFactory
{
    public Button createButton();

    public TextView createTextView();
}

class RedSkinFactory implements SkinFactory
{
    @Override
    public Button createButton()
    {
        return new RedButton();
    }

    @Override
    public TextView createTextView()
    {
        return new RedTextView();
    }
}

class GreenSkinFactory implements SkinFactory
{
    @Override
    public Button createButton()
    {
        return new GreenButton();
    }
    @Override
    public TextView createTextView()
    {
        return new GreenTextView();
    }

}

interface Button
{
    public void paint();
}

interface TextView
{
    public void paint();
}

class RedButton implements Button
{
    @Override
    public void paint()
    {
        System.out.println("画一个红色按钮");
    }
}

class GreenButton implements Button
{
    @Override
    public void paint()
    {
        System.out.println("画一个绿色按钮");
    }
}

class RedTextView implements TextView
{
    @Override
    public void paint()
    {
        System.out.println("画一个红色文本");
    }
}

class GreenTextView implements TextView
{
    @Override
    public void paint()
    {
        System.out.println("画一个绿色文本");
    }
}

单例(Singleton)

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

优点:

  • 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能
  • 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题

缺点:

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起
  • 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失

适用场景

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例

例子:

饿汉式单例

class EagerSingleton
{
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton(){}

    public static EagerSingleton getInstance()
    {
        return instance;
    }
}

懒汉式单例(DCL方式)

class LazySingleton {   
    //没有volatile修饰符,可能出现Java中的另一个线程看到个初始化了一半的LazySingleton的情况
    private volatile static LazySingleton instance = null;   

    private LazySingleton() { }   

    public static LazySingleton getInstance() 
    {   
        //第一重判断  
        if (instance == null) 
        {  
            //锁定代码块  
            synchronized (LazySingleton.class) 
            {  
                //第二重判断  
                if (instance == null) {  
                    instance = new LazySingleton(); //创建单例实例  
                }  
            }  
        }  
        return instance;   
    }  
}

登记式单例

class RegSingleton
{
    static private HashMap<String, RegSingleton> mRegistry = new HashMap<String, RegSingleton>();
    static 
    {
        RegSingleton x = new RegSingleton();
        mRegistry.put(x.getClass().getName(), x);
    }

    protected RegSingleton(){};

    static public RegSingleton getInstance(String name)
    {
        if(name == null)
        {
            name = "com.singleton.RegSingleton";//即包名
        }
        if(mRegistry.get(name) == null)
        {
            try
            {
                mRegistry.put(name, (RegSingleton) Class.forName(name).newInstance());
            } catch (Exception e)
            {
                System.out.println("Error");
            }
        }
        return mRegistry.get(name);
    }

}

class RegSingletonChild extends RegSingleton
{
    public RegSingletonChild(){}

    static public RegSingletonChild getInstance()
    {
        return (RegSingletonChild) RegSingletonChild.getInstance("com.singleton.RegSingletonChild");
    }
}

IoDH单例


//IoDH,即使用内部类(与编程语言本身的特性相关,很多面向对象语言不支持IoDH)
class Singleton {  
    private Singleton() {  
    }  

    private static class HolderClass {  
            private final static Singleton instance = new Singleton();  
    }  

    public static Singleton getInstance() {  
        return HolderClass.instance;  
    }  

}

原型(Prototype)

使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

简单形式的原始模型的一般性结构

  • 抽象原型:它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
  • 具体原型:它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象
  • 客户::让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

登记形式的原始模型的一般性结构

  • 客户:客户端类向管理员提出创建对象的请求
  • 抽象原型:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体原型类所需的接口
  • 具体原型:被复制的对象。需要实现抽象的原型角色所要求的接口
  • 原型管理器:创建具体原型类的对象,并记录每一个被创建的对象,
    优点
  • 当创建新的对象实例比较复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  • 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响
  • 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类登记结构相同的工厂等级结构,而原型模式就不要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  • 可以使用深克隆的方法保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

缺点

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,但对已有的类进行改造时,需要修改源代码,违背”开闭原则”
  • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

适应场景

  • 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  • 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

简单形式的例子


public class Client
{
    private Prototype prototype;

    public void operation(Prototype example)
    {
        Prototype p = example.clone();
    }
}

interface Prototype extends Cloneable
{
    Prototype clone();
}

class ConcretePrototype implements Prototype
{
    public Prototype clone()
    {
        try
        {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e)
        {
            return null;
        }
    }
}

登记形式的例子


import java.util.Vector;

public class Client
{

    private PrototypeManager pm;
    private Prototype prototype;

    public void registerPrototype()
    {
        prototype = new ConcretePrototype();
        Prototype copytype = (Prototype) prototype.clone();
        pm.add(copytype);
    }
}

interface Prototype extends Cloneable
{
    public Object clone();
}

class ConcretePrototype implements Prototype
{
    public synchronized Object clone()
    {
        Prototype temp = null;
        try
        {
            temp = (Prototype) super.clone();
            return temp;
        } catch (CloneNotSupportedException e)
        {
            System.out.println("Clone failed");
        }
        finally
        {
            return temp;
        }
    }
}

class PrototypeManager
{
    private Vector<Prototype> objects = new Vector<Prototype>();

    public void add(Prototype object)
    {
        objects.add(object);
    }

    public Prototype get(int i)
    {
        return objects.get(i);
    }

    public int getSize()
    {
        return objects.size();
    }
}

建造者(Builder)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

一般性结构

  • Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
  • ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
  • Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。
  • Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

优点:

  • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”
  • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。

适用场景

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

例子:

public class Test
{
    public static void main(String[] args)
    {
        Director director = new Director(new MacbookBuilder());
        Computer computer = director.construct("英特尔主板", "Retina显示器");
    }
}


abstract class Computer
{
    protected String mBoard;
    protected String mDisplay;
    protected String mOS;


    protected Computer()    {}

    public void setBoard(String board)
    {
        mBoard = board;
    }

    public void setDisplay(String display)
    {
        mDisplay = display;
    }

    public void setOS(String OS)
    {
        mOS = OS;
    }

}


class Macbook extends Computer
{
    protected Macbook(){}
}

class Lenovo extends Computer
{
    protected Lenovo(){}
}

abstract class Builder
{
    public abstract void buildBoard(String board);

    public abstract void buildDisplay(String display);

    public  abstract void buildOS();

    public abstract Computer create();

}

class MacbookBuilder extends Builder
{
    private static final String MACOSX = "Mac OS X 10.10";
    private Computer mComputer = new Macbook();

    @Override
    public void buildBoard(String board)
    {
        mComputer.setBoard(board);
    }

    @Override
    public void buildDisplay(String display)
    {
        mComputer.setDisplay(display);
    }

    @Override
    public void buildOS()
    {
        mComputer.setOS(MACOSX);
    }

    @Override
    public Computer create()
    {
        return mComputer;
    }

}

class LenovoBuilder extends Builder
{
    private static final String WINDOWS = "Windows 10";
    private Computer mComputer = new Macbook();

    @Override
    public void buildBoard(String board)
    {
        mComputer.setBoard(board);
    }

    @Override
    public void buildDisplay(String display)
    {
        mComputer.setDisplay(display);
    }

    @Override
    public void buildOS()
    {
        mComputer.setOS(WINDOWS);
    }

    @Override
    public Computer create()
    {
        return mComputer;
    }

}

class Director
{
    Builder mBuilder = null;

    public Director(Builder builder)
    {
        mBuilder = builder;
    }

    public Computer construct(String board,String display)
    {
        mBuilder.buildBoard(board);
        mBuilder.buildDisplay(display);
        mBuilder.buildOS();
        return mBuilder.create();
    }
}

省略指挥者与抽象建造者

public class Test
{
    public static void main(String[] args)
    {
        NutritionFacts.Builder builder = new NutritionFacts.Builder(1, 1);
        NutritionFacts nutritionFacts = builder.calories(1).fat(1).build();
    }
}


class NutritionFacts
{
    private final int servngSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodiun;
    private final int carbohydrate;

    public static class Builder
    {
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;

        public Builder(int servingSize,int servings)
        {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val)
        {
            calories = val;
            return this;
        }

        public Builder fat(int val)
        {
            fat = val;
            return this;
        }

        public Builder carbohydrate(int val)
        {
            carbohydrate = val;
            return this;
        }

        public Builder sodium(int val)
        {
            sodium = val;
            return this;
        }

        public NutritionFacts build()
        {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder)
    {
        servngSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodiun = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}