设计模式

Posted wangziqiang123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式相关的知识,希望对你有一定的参考价值。

一、单例模式–确保对象的唯一性

目的

确保对象的唯一性。

原理

使用一个私有构造函数、一个私有静态变量以及一个公有静态方法来实现。

示例

  1. 懒汉式
    public class LazySingleton {
     private static LazySingleton instance;//私有静态变量
    
     /**
     * 私有构造器
     */
     private LazySingleton(){}
    
     /**
      * 提供获取单例实例的静态方法
      * 该方法可以保证线程安全,但在多线程高并发环境中系统性能低
      * @return 单例实例对象
      */
     synchronized public static LazySingleton getInstance(){
         if (instance == null){
             instance = new LazySingleton();
         }
    
         return instance;
     }
    }
    

    该方法可以确保只实例化一个单例对象。但在多线程高并发环境中系统性能低,当第一个线程进入了getInstance()方法后,其他线程只能等待。

  2. 双重检查锁
    public class DoubleCheckSingleton {
     private static volatile DoubleCheckSingleton instance;
    
     private DoubleCheckSingleton(){}
    
     public static DoubleCheckSingleton getInstance(){
         //第一重判断
         if (instance == null){
             //锁定代码块
             synchronized (DoubleCheckSingleton.class){
                 //第二重判断
                 if (instance == null){
                     instance = new DoubleCheckSingleton();
                 }
             }
         }
         return instance;
     }
    }
    

    为什么需要判断两次呢?第一重判断是为了避免在高并发多线程环境中多线程等待造成的系统性能问题。第二重判断是为了确保只生成一个单例对象,倘若我们把第二重判断去掉,那么在某一时刻,A线程和B线程同时进入了第一重判断并且此时instance实例还没有生成,最终就会生成两个instance实例,所以两重判断一个都不能少。 双重检查锁方法,解决了线程安全的懒汉式生成单例对象的高并发系统性能问题。但是需要注意的是使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。

相关知识参考:Java并发编程:volatile关键字解析

  1. 饿汉式
    /**
     * Created by sunmood on 2018/12/14.
     * 饿汉式单例
     * 当类被加载时,静态变量instance会被初始化,
     * 此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
     */
    public class EagerSingleton {
     private static final EagerSingleton instance = new EagerSingleton();
    
     private EagerSingleton(){}
    
     public static EagerSingleton getInstance(){
         return instance;
     }
    }
    

    该方法是在类加载的时候就实例化一个单例对象,当线程调用时直接返回已经生成的实例。该方法没有延迟实例化带来的节约资源的好处。

  2. 静态内部类 饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们来学习这种更好的被称之为Initialization Demand Holder (IoDH)的技术。
/**
 * Created by sunmood on 2018/12/15.
 * 单例类中增加一个静态(static)内部类,
 * 在该内部类中创建单例对象,
 * 再将该单例对象通过getInstance()方法返回给外部使用
 */
public class IoDHSingleton {
    private IoDHSingleton(){}

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

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

    public static void main(String[] args) {
        IoDHSingleton s1,s2;
        s1 = IoDHSingleton.getInstance();
        s2 = IoDHSingleton.getInstance();
        System.out.println(s1 == s2);
    }
}

编译并运行上述代码,运行结果为:true,即创建的单例对象s1和s2为同一对象。由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

二、原型模式–对象的克隆

目的

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。

原理

原型模式主要包含如下几个角色:

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

原型模式的核心在于如何实现克隆方法。

相关知识参考:浅克隆、深克隆、对象序列化、数据流

示例

下面通过模拟一个简单的公文管理器来介绍原型管理器的设计与实现: Sunny软件公司在日常办公中有许多公文需要创建、递交和审批,例如《可行性分析报告》、《立项建议书》、《软件需求规格说明书》、《项目进展报告》等,为了提高工作效率,在OA系统中为各类公文均创建了模板,用户可以通过这些模板快速创建新的公文,这些公文模板需要统一进行管理,系统根据用户请求的不同生成不同的新公文。

其结构如图所示: 技术图片

核心代码:

/**
 * Created by sunmood on 2018/12/18.
 * 抽象文档接口
 */
public interface OfficeDocument extends Cloneable {
    OfficeDocument clone();
    void display();
}

/**
 * Created by sunmood on 2018/12/18.
 * 可行性分析报告类
 */
public class FAR implements OfficeDocument {
    public OfficeDocument clone() {
        OfficeDocument far = null;

        try {
            far = (OfficeDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("不支持复制!");
        }
        return far;
    }

    public void display() {
        System.out.println("《可行性分析报告》");
    }
}

/**
 * Created by sunmood on 2018/12/18.
 * 软件需求规格说明书
 */
public class SRS implements OfficeDocument {
    public OfficeDocument clone() {
        OfficeDocument srs = null;

        try {
            srs = (OfficeDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("不支持复制!");
        }
        return srs;
    }

    public void display() {
        System.out.println("《软件需求规格说明书》");
    }
}


/**
 * Created by sunmood on 2018/12/18.
 * 原型管理器
 * 使用饿汉式单例实现
 */
public class PrototypeManager {
    private Hashtable hashtable = new Hashtable();
    private static PrototypeManager prototypeManager = new PrototypeManager();

    private PrototypeManager(){
        hashtable.put("far",new FAR());
        hashtable.put("srs",new SRS());
    }

    /**
     * 增加新的公文对象
     * @param key
     * @param document
     */
    public void addOfficeDocument(String key, OfficeDocument document){
        hashtable.put(key,document);
    }

    /**
     * 通过浅克隆获取新的对象
     * @param key
     * @return
     */
    public OfficeDocument getOfficeDocument(String key){
        return ((OfficeDocument) hashtable.get(key)).clone();
    }

    public static PrototypeManager getPrototypeManager(){
        return prototypeManager;
    }
}

客户端测试类:

public class Client {
    public static void main(String[] args) {
        //获取原型管理器对象
        PrototypeManager pm =  PrototypeManager.getPrototypeManager();

        OfficeDocument  doc1,doc2,doc3,doc4;

        doc1  = pm.getOfficeDocument("far");
        doc1.display();
        doc2  = pm.getOfficeDocument("far");
        doc2.display();
        System.out.println(doc1  == doc2);

        doc3  = pm.getOfficeDocument("srs");
        doc3.display();
        doc4  = pm.getOfficeDocument("srs");
        doc4.display();
        System.out.println(doc3  == doc4);
    }
}

输出结果:

《可行性分析报告》
《可行性分析报告》
false
《软件需求规格说明书》
《软件需求规格说明书》
false

优缺点

  1. 主要优点:
    1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
    2. 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
    3. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
    4. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
  2. 主要缺点:
    1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
    2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

应用场景

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

三、简单工厂模式

目的

提供一个工厂方法,通过传入一个参数获取相应的对象实例,隐藏对象创建的内部细节。

原理

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

简单工厂模式主要包含如下几个角色:

  • Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。
  • Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
  • ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。

示例

Sunny软件公司欲基于Java语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,例如柱状图、饼状图、折线图等。Sunny软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。

结构设计如下图所示: 技术图片

图形产品类核心代码:

/**
 * Created by sunmood on 2018/12/13.
 * 抽象图形类
 */
public interface Graphic {
    /**
     * 绘制图形
     */
    void draw();

    /**
     * 擦除图形
     */
    void erase();
}
/**
 * Created by sunmood on 2018/12/13.
 * 圆形类
 */
public class CircleGraphic implements Graphic {
    public void draw() {
        System.out.println("绘制圆形");
    }

    public void erase() {
        System.out.println("擦除圆形");
    }
}
/**
 * Created by sunmood on 2018/12/13.
 * 方形类
 */
public class RectangleGraphic implements Graphic {
    public void draw() {
        System.out.println("绘制方形");
    }

    public void erase() {
        System.out.println("擦除方形");
    }
}
/**
 * Created by sunmood on 2018/12/13.
 * 三角形类
 */
public class TriangleGraphic implements Graphic {
    public void draw() {
        System.out.println("绘制三角形");
    }

    public void erase() {
        System.out.println("擦除三角形");
    }
}

图形工厂类核心代码:

/**
 * Created by sunmood on 2018/12/13.
 * 图形工厂类
 */
public class GraphicFactory {
    public static Graphic getGraphic(String type){
        if (type.equalsIgnoreCase("circle")){
            return new CircleGraphic();
        } else if (type.equalsIgnoreCase("triangle")){
            return new TriangleGraphic();
        } else if (type.equalsIgnoreCase("rectangle")){
            return new RectangleGraphic();
        } else {
            throw new UnSupportedShapeException("不支持的图形!");
        }
    }
}

测试方法:

@Test
public void testGetGraphic() throws Exception {
    Graphic graphic = GraphicFactory.getGraphic("circle");
    graphic.draw();
    graphic.erase();
} 

输出结果:

绘制圆形
擦除圆形

优缺点

  1. 主要优点:
    1. 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
    2. 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
    3. 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
  2. 主要缺点:
    1. 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
    2. 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
    3. 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
    4. 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

      应用场景

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

四、工厂方法模式

目的

定义了一个创建对象实例的工厂方法接口,但由具体工厂子类决定要创建哪个对象实例。工厂方法模式把实例化推迟到子类。

原理

工厂方法模式主要包含如下几个对象:

  • Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
  • Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  • ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类。具体产品由其对应的具体工厂负责生产。

示例

使用工厂方法模式设计一个程序来读取各种不同类型的图片格式,针对每一种图片格式都设计一个图片读取器,如GIF图片读取器用于读取GIF格式的图片、JPG图片读取器用于读取JPG格式的图片。

设计结构图如下所示: 技术图片

产品类核心代码:

/**
 * Created by sunmood on 2018/12/14.
 * 图片读取器接口
 */
public interface PictureReader {
    void read();
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class JPGReader implements PictureReader {
    public void read() {
        System.out.println("读取JPG图片。。。");
    }
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class GIFReader implements PictureReader {
    public void read() {
        System.out.println("读取GIF图片。。。");
    }
}

工厂类核心代码:

/**
 * Created by sunmood on 2018/12/14.
 * 工厂接口
 */
public abstract class PictureReaderFactory {
    public void read(){
        PictureReader reader = this.getPictureReader();
        reader.read();
    }
    public abstract PictureReader getPictureReader();
}
/**
 * Cre 大专栏  设计模式ated by sunmood on 2018/12/14.
 */
public class JPGReaderFactory extends PictureReaderFactory {
    public PictureReader getPictureReader() {
        return new JPGReader();
    }
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class GIFReaderFactory extends PictureReaderFactory {
    public PictureReader getPictureReader() {
        return new GIFReader();
    }
}

测试方法:

@Test
public void testGetPictureReader() throws Exception {
    PictureReaderFactory factory = new GIFReaderFactory();
    PictureReader reader = factory.getPictureReader();
    reader.read();
} 

输出结果:

读取GIF图片。。。

优缺点

  1. 主要优点:
    1. 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
    2. 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
    3. 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
  2. 主要缺点:
    1. 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
    2. 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

应用场景

  1. 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  2. 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

五、抽象工厂模式

目的

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

原理

抽象工厂模式主要包含如下几个角色:

  • AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
  • ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
  • AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  • ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

示例

Sunny软件公司欲推出一款新的手机游戏软件,该软件能够支持iosandroid等多个智能手机操作系统平台,针对不同的手机操作系统,该游戏软件提供了不同的游戏操作控制(OperationController)类和游戏界面控制(InterfaceController)类,并提供相应的工厂类来封装这些类的初始化过程。软件要求具有较好的扩展性以支持新的操作系统平台。

软件结构设计如下图所示: 技术图片

产品类核心代码:

/**
 * Created by sunmood on 2018/12/14.
 * 抽象游戏控制界面类
 */
public abstract class InterfaceController {
    /**
     * 界面显示方法
     */
    public abstract void display();
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class AndroidInterfaceController extends InterfaceController {
    public void display() {
        System.out.println("安卓系统游戏界面。。。");
    }
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class IOSInterfaceController extends InterfaceController {
    public void display() {
        System.out.println("IOS游戏界面。。。");
    }
}
/**
 * Created by sunmood on 2018/12/14.
 * 抽象游戏操作控制类
 */
public abstract class OperationController {
    /**
     * 游戏控制器方法
     */
    public abstract void operate();
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class AndroidOperationController extends OperationController {
    public void operate() {
        System.out.println("安卓系统游戏控制。。。");
    }
}
/**
 * Created by sunmood on 2018/12/14.
 */
public class IOSOperationController extends OperationController {
    public void operate() {
        System.out.println("IOS游戏控制。。。");
    }
}

工厂类核心代码:

/**
 * Created by sunmood on 2018/12/14.
 * 游戏产品抽象工厂
 */
public abstract class GameFactory {
    public abstract OperationController getOperationController();
    public abstract InterfaceController getInterfaceController();
}
/**
 * Created by sunmood on 2018/12/14.
 * 安卓游戏工厂类
 */
public class AndroidGameFactory extends GameFactory {
    public OperationController getOperationController() {
        return new AndroidOperationController();
    }

    public InterfaceController getInterfaceController() {
        return new AndroidInterfaceController();
    }
}
/**
 * Created by sunmood on 2018/12/14.
 * IOS游戏工厂类
 */
public class IOSGameFactory extends GameFactory {
    public OperationController getOperationController() {
        return new IOSOperationController();
    }

    public InterfaceController getInterfaceController() {
        return new IOSInterfaceController();
    }
}

测试代码:

    @Test
    public void testGameFactory() throws Exception {
        GameFactory gameFactory = new AndroidGameFactory();
        InterfaceController interfaceController = gameFactory.getInterfaceController();
        OperationController operationController = gameFactory.getOperationController();

        interfaceController.display();
        operationController.operate();
    }

输出结果:

安卓系统游戏界面。。。
安卓系统游戏控制。。。

使用抽象工厂模式来设计,让我们可以在不修改原有代码的基础上添加新的产品族。比如,我们现在需要让该游戏支持Windows mobile系统,我们只需要新建Windows mobile的界面类、控制器类、具体工厂方法就可以对系统软件进行扩展,符合“开闭原则”。 但是,如果我们现在要增加一个产品等级,就需要修改原有代码,违背了“开闭原则”。 正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。

优缺点

  1. 主要优点:
    1. 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
    2. 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
    3. 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
  2. 主要缺点:
    1. 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

应用场景

  1. 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
  2. 系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
  3. 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
  4. 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

六、建造者模式

目的

复杂对象的组装与创建

原理

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。

建造者模式主要包含如下几个角色:

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

示例

Sunny软件公司欲开发一个视频播放软件,为了给用户使用提供方便,该播放软件提供多种界面显示模式,如完整模式、精简模式、记忆模式、网络模式等。在不同的显示模式下主界面的组成元素有所差异,如在完整模式下将显示菜单、播放列表、主窗口、控制条等,在精简模式下只显示主窗口和控制条,而在记忆模式下将显示主窗口、控制条、收藏列表等。

软件设计结构图如下所示: 技术图片

播放软件产品类:

/**
 * Created by sunmood on 2018/12/17.
 * 播放器界面
 */
public class PlayerInterface {
    private String menu;//菜单
    private String playList;//播放列表
    private String mainWindow;//主窗口
    private String controlBar;//控制条
    private String favoriteList;//收藏列表

    @Override
    public String toString() {
        return "player.PlayerInterface{" +
                "menu='" + menu + ''' +
                ", playList='" + playList + ''' +
                ", mainWindow='" + mainWindow + ''' +
                ", controlBar='" + controlBar + ''' +
                ", favoriteList='" + favoriteList + ''' +
                '}';
    }

   //getter、setter方法。。。
   
}

建造者类

/**
 * Created by sunmood on 2018/12/17.
 * 播放器界面建造者抽象类
 */
public abstract class PlayerInterfaceBuilder {
    protected PlayerInterface playerInterface = new PlayerInterface();

    public abstract void buildMenu();
    public abstract void buildPlayList();
    public abstract void buildMainWindow();
    public abstract void buildControlBar();
    public abstract void buildFavoriteList();

    //钩子方法
    public boolean isBuildMenu(){
        return true;
    }

    public boolean isBuildPlayList(){
        return true;
    }

    public boolean isBuildFavoriteList(){
        return true;
    }

    public PlayerInterface createPlayerInterface(){
        return playerInterface;
    }
}

/**
 * Created by sunmood on 2018/12/17.
 * 记忆模式建造者
 */
public class FavoritePatternBuilder extends PlayerInterfaceBuilder {
    public void buildMenu() {
        playerInterface.setMenu("菜单");
    }

    public void buildPlayList() {
        playerInterface.setPlayList("播放列表");
    }

    public void buildMainWindow() {
        playerInterface.setMainWindow("主窗口");
    }

    public void buildControlBar() {
        playerInterface.setControlBar("控制条");
    }

    public void buildFavoriteList() {
        playerInterface.setFavoriteList("收藏列表");
    }

    @Override
    public boolean isBuildMenu() {
        return false;
    }

    @Override
    public boolean isBuildPlayList() {
        return false;
    }

    @Override
    public boolean isBuildFavoriteList() {
        return super.isBuildFavoriteList();
    }
}
/**
 * Created by sunmood on 2018/12/17.
 * 完整模式建造者
 */
public class FullPatternBuilder extends PlayerInterfaceBuilder {

    public void buildMenu() {
        playerInterface.setMenu("菜单");
    }

    public void buildPlayList() {
        playerInterface.setPlayList("播放列表");
    }

    public void buildMainWindow() {
        playerInterface.setMainWindow("主窗口");
    }

    public void buildControlBar() {
        playerInterface.setControlBar("控制条");
    }

    public void buildFavoriteList() {
        playerInterface.setFavoriteList("收藏列表");
    }
}
/**
 * Created by sunmood on 2018/12/17.
 * 精简模式建造者
 */
public class SimplePatternBuilder extends PlayerInterfaceBuilder {

    public void buildMenu() {
        playerInterface.setMenu("菜单");
    }

    public void buildPlayList() {
        playerInterface.setPlayList("播放列表");
    }

    public void buildMainWindow() {
        playerInterface.setMainWindow("主窗口");
    }

    public void buildControlBar() {
        playerInterface.setControlBar("控制条");
    }

    public void buildFavoriteList() {
        playerInterface.setFavoriteList("收藏列表");
    }

    @Override
    public boolean isBuildMenu() {
        return false;
    }

    @Override
    public boolean isBuildPlayList() {
        return false;
    }

    @Override
    public boolean isBuildFavoriteList() {
        return false;
    }
}

指挥者类:

/**
 * Created by sunmood on 2018/12/17.
 * 指挥者类
 */
public class PlayerInterfaceController {
    public PlayerInterface construct(PlayerInterfaceBuilder playerInterfaceBuilder){
        PlayerInterface playerInterface;

        playerInterfaceBuilder.buildMainWindow();
        playerInterfaceBuilder.buildControlBar();
        if (playerInterfaceBuilder.isBuildFavoriteList()){
            playerInterfaceBuilder.buildFavoriteList();
        }
        if (playerInterfaceBuilder.isBuildMenu()){
            playerInterfaceBuilder.buildMenu();
        }
        if (playerInterfaceBuilder.isBuildPlayList()){
            playerInterfaceBuilder.buildPlayList();
        }
        playerInterface = playerInterfaceBuilder.createPlayerInterface();

        return playerInterface;
    }
}

客户端测试类:

public class Client {
    public static void main(String[] args) {
        PlayerInterfaceController controller = new PlayerInterfaceController();
        PlayerInterface playerInterface = controller.construct(new FavoritePatternBuilder());
        System.out.println("记忆模式:" + playerInterface.toString());
        playerInterface = controller.construct(new FullPatternBuilder());
        System.out.println("完整模式:" + playerInterface.toString());
        playerInterface = controller.construct(new SimplePatternBuilder());
        System.out.println("精简模式:" + playerInterface.toString());
    }
}

输出结果:

记忆模式:player.PlayerInterface{menu='null', playList='null', mainWindow='主窗口', controlBar='控制条', favoriteList='收藏列表'}
完整模式:player.PlayerInterface{menu='菜单', playList='播放列表', mainWindow='主窗口', controlBar='控制条', favoriteList='收藏列表'}
精简模式:player.PlayerInterface{menu='null', playList='null', mainWindow='主窗口', controlBar='控制条', favoriteList='null'}

建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品,在软件开发中,如果我们需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。

优缺点

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

应用场景

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

以上是关于设计模式的主要内容,如果未能解决你的问题,请参考以下文章

十条实用的jQuery代码片段

尝试使用片段保存夜间模式状态

如何更改谷歌地图标记上方的标题和片段设计

炫酷 CSS 背景效果的 10 个代码片段

添加片段时的 FlyOut 菜单设计问题

高效Web开发的10个jQuery代码片段