设计模式之创建型模式

一、简介

创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。

创建型模式(Creational Pattern)

  • 单例模式 Singleton Pattern: 单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
    使用频率:★★★★☆

  • 简单工厂模式 Simple Factory Pattern: 又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
    使用频率:★★★☆☆

  • 工厂方法模式 Factory Method Pattern: 是一种常用的类创建型设计模式,此模式的核心精神是封装类中不变的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。
    使用频率:★★★★★

  • 抽象工厂模式 Abstract Factory Pattern: 是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。
    使用频率:★★★★★

  • 原型模式 Prototype Pattern: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
    使用频率:★★★☆☆

  • 建造者模式 Builder Pattern: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
    使用频率:★★☆☆☆

二、单例模式

2.1、饿汉式

/**
 * 单例模式(饿汉式)
 * 线程安全,没有加锁,执行效率会提高。
 * 类加载时就初始化,浪费内存,容易产生垃圾对象
 * <p>
 * 单例类使用了final修饰,防止被继承
 * 这种写法比较简单,就是在类装载的时候就完成实例化。instance作为类变量,在类初始化过程中,会被收集进<clinit>()方法中,该方法会保障同步,从而避免了线程同步问题。
 * 在类装载的时候就完成实例化,若从未使用过这个实例,则会造成内存的浪费。
 */
public final class SingleObject1 {
    //创建 SingleObject1 的一个对象
    private static SingleObject1 instance = new SingleObject1();
    //私有化构造函数,这样该类就不会被实例化
    private SingleObject1() {}
    //获取唯一可用的对象
    public static SingleObject1 getInstance() {
        return instance;
    }
    public void say() {
        System.out.println("Hello");
    }
}

……
//调用
SingleObject1.getInstance().say();//Hello

2.2、懒汉式,线程不安全

/**
 * 单例模式(懒汉式,线程不安全)
 * 线程不安全,没有加锁。
 * 此方式在确定是单线程的情况下,可以保证创建的对象唯一性
 */
public final class SingleObject2 {
    //创建 SingleObject1 的一个对象
    private static SingleObject2 instance;
    //私有化构造函数,这样该类就不会被实例化
    private SingleObject2() {}
    //获取唯一可用的对象
    public static SingleObject2 getInstance() {
        if (instance == null) {
            instance = new SingleObject2();
        }
        return instance;
    }
    public void say() {
        System.out.println("Hello");
    }
}

……
//调用
SingleObject2.getInstance().say();//Hello

2.3、懒汉式,线程安全

/**
 * 单例模式(懒汉式,线程安全)
 * 线程安全,加锁,执行效率低
 */
public final class SingleObject3 {
    //创建 SingleObject1 的一个对象
    private static SingleObject3 instance;
    //私有化构造函数,这样该类就不会被实例化
    private SingleObject3() {}
    //获取唯一可用的对象
    public synchronized static  SingleObject3 getInstance() {
        if (instance == null) {
            instance = new SingleObject3();
        }
        return instance;
    }
    public void say() {
        System.out.println("Hello");
    }
}

……
//调用
SingleObject3.getInstance().say();//Hello

2.4、双检锁/双重校验锁

/**
 * 单例模式(双检锁/双重校验锁)
 * 线程安全
 * 种方式采用双锁机制,安全且在多线程情况下能保持高性能
 */
public final class SingleObject4 {
    //创建 SingleObject1 的一个对象
    private volatile static SingleObject4 instance;
    //私有化构造函数,这样该类就不会被实例化
    private SingleObject4() {}
    //获取唯一可用的对象
    public static SingleObject4 getInstance() {
        if (instance == null) {
            synchronized (SingleObject4.class) {
                if (instance == null) {
                    instance = new SingleObject4();
                }
            }
        }
        return instance;
    }
    public void say() {
        System.out.println("Hello");
    }
}

……
//调用
SingleObject4.getInstance().say();//Hello

2.5、静态内部类方式

/**
 * 单例模式(静态内部类实现模式)
 * 线程安全,调用效率高,可以延时加载
 * 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。
 * 这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
 * 这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,
 * <p>
 * 静态内部类方式是最好也是最常用的几种实现单例模式的方式之一
 */
public final class SingleObject5 {

    private static class SingleObject5Holder {
        private static final SingleObject5 instance = new SingleObject5();
    }
    //私有化构造函数,这样该类就不会被实例化
    private SingleObject5() {}
    //获取唯一可用的对象
    public static SingleObject5 getInstance() {
        return SingleObject5Holder.instance;
    }
    public void say() {
        System.out.println("Hello");
    }
}

……
//调用单例
SingleObject5.getInstance().say();//Hello

2.6、枚举方式

/**
 * 单例模式(枚举方式,Android不推荐)
 * 线程安全,调用效率高,不能延时加载,可以防止反射和反序列化调用
 * 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
 * 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,
 * 防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
 * 不能通过 reflection attack 来调用私有构造方法。
 *
 * 枚举实现单例,不推荐在Android平台使用,因为内存消耗会其他方式多一些,Android官方也不推荐枚举,
 * Android平台推荐双重校验或者静态内部类单例,现在的Android开发环境jdk一般都大于1.5了。所以volatile的问题不必担心。
 * Java平台开发的Effective Java一书中推荐使用枚举实现单例,可以保证效率,而且还能解决反序列化创建新对象的问题。
 */
public  enum SingleObject6 {
    INSTANCE;

    public void say() {
        System.out.println("Hello");
    }
}

……
//调用单例
SingleObject6.INSTANCE.say();//Hello

三、简单工厂模式

3.1、定义接口

定义人接口,吃的动作方法.

public interface Human {
  	//吃动作
    void eat();
}

3.2、定义具体实现

定义中国人.

public class Chinese implements Human {
    @Override
    public void eat() {
        System.out.println("中国人吃饭");
    }
}

定义美国人.

public class American implements Human {
    @Override
    public void eat() {
        System.out.println("美国人吃饭");
    }
}

3.3、定义工厂类

方式一: 通过type创建工厂类

public class HumanFactory {
    public static String CHINESE_HUMAN = "CHINESE_HUMAN";
    public static String AMERICAN_HUMAN = "AMERICAN_HUMAN";

    /**
     * 创建人类工厂方法
     * @param type {@link #CHINESE_HUMAN}、{@link #AMERICAN_HUMAN}
     * @return 人类
     */
    public Human createHuman(@NotNull String type) {
        if (type.equals(CHINESE_HUMAN)) {
            return new Chinese();
        } else if (type.equals(AMERICAN_HUMAN)) {
            return new American();
        }
        return null;
    }
}

方式二: 通过java反射方式创建工厂类

public class HumanFactory2 {
    /**
     * 创建人类工厂方法
     * @param c 中国人、美国人
     * @param <T> 
     * @return 人类
     */
    public <T extends Human> T  createHuman(Class<T> c) {
        T t=null;
        try {
            t = (T) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            System.out.println("不支持抽象类或接口");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.out.println("没有足够权限,即不能访问私有对象");
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            System.out.println("类不存在");
            e.printStackTrace();
        }
        return t;
    }
}

3.5、使用简单工厂模式

public static void main(String[] args) {
    //----------------------方式一------------------------------
    //创建工厂类
    HumanFactory humanFactory=new HumanFactory();
    //创建美国人
    Human human1 = humanFactory.createHuman(HumanFactory.AMERICAN_HUMAN);
    human1.eat();//美国人吃饭
    //创建中国人
    Human human2 = humanFactory.createHuman(HumanFactory.CHINESE_HUMAN);
    human2.eat();//中国人吃饭
    
    //----------------------方式二------------------------------
     HumanFactory2 humanFactory2 = new HumanFactory2();
     Human human3 = humanFactory2.createHuman(American.class);
     human3.eat();//美国人吃饭
     Human human4 = humanFactory2.createHuman(Chinese.class);
     human4.eat();//中国人吃饭
}

四、工厂方法模式

4.1、定义接口

定义人接口,吃的动作方法.

public interface Human {
  	//吃动作
    void eat();
}

定义工厂类接口

//工厂接口类
public interface Factory {
    //创建人类
    Human createHuman();
}

4.2、定义具体实现

定义中国人.

public class Chinese implements Human {
    @Override
    public void eat() {
        System.out.println("中国人吃饭");
    }
}

定义美国人.

public class American implements Human {
    @Override
    public void eat() {
        System.out.println("美国人吃饭");
    }
}

定义中国人工厂类

//中国人工厂类
public class ChineseFactory implements Factory {
    @Override
    public Human createHuman() {
        return new Chinese();
    }
}

定义美国人工厂类

//美国人工厂类
public class AmericanFactory implements Factory{
    @Override
    public Human createHuman() {
        return new American();
    }
}

4.3、使用工厂方法模式

public static void main(String[] args) {
    Factory factory1 = new AmericanFactory();
    Human human1 = factory1.createHuman();
    human1.eat();//美国人吃饭

    Factory factory2 = new ChineseFactory();
    Human human2 = factory2.createHuman();
    human2.eat();//中国人吃饭
}

五、抽象工厂模式

5.1、定义接口

定义人接口,吃的动作方法.

public interface Human {
  	//吃动作
    void eat();
}

定义水果接口

//水果接口
public interface Fruits {
    //水果名称
    String name();
}

定义工厂接口,每个工厂都有制造人和苹果方法

//工厂接口
public interface Factory {
    //创建人类
    Human createHuman();
    //创建水果
    Fruits createFruits();
}

5.2、定义具体实现

定义中国人

public class Chinese implements Human {
    @Override
    public void eat() {
        System.out.println("中国人吃饭");
    }
}

定义美国人

public class American implements Human {
    @Override
    public void eat() {
        System.out.println("美国人吃饭");
    }
}

定义苹果

//苹果实体
public class Apple implements Fruits{
    @Override
    public String name() {
        System.out.println("苹果");
        return "苹果";
    }
}

定义葡萄

//葡萄实体
public class Grape implements Fruits {
    @Override
    public String name() {
        System.out.println("葡萄");
        return "葡萄";
    }
}

定义制造中国人和苹果的工厂

//制造工厂A 制造中国人和苹果
public class CreateFactoryA  implements Factory{
    @Override
    public Human createHuman() {
        return new Chinese();
    }
    @Override
    public Fruits createFruits() {
        return new Apple();
    }
}

定义制造美国人和葡萄的工厂

//制造工厂B 制造美国人和葡萄
public class CreateFactoryB implements Factory{
    @Override
    public Human createHuman() {
        return new American();
    }
    @Override
    public Fruits createFruits() {
        return new Grape();
    }
}

5.3、使用抽象工厂模式

  public static void main(String[] args) {
        Factory factory1=new CreateFactoryA();
        Human human = factory1.createHuman();
        Fruits fruits = factory1.createFruits();
        human.eat();//中国人吃饭
        fruits.name();//苹果

        Factory factory2=new CreateFactoryB();
        Human human2 = factory2.createHuman();
        Fruits fruits2 = factory2.createFruits();
        human2.eat();//美国人吃饭
        fruits2.name();//葡萄
    }

六、原型模式

原型模式是一个创建型的模式。原型二字表明了改模式应该有一个样板实例,用户从这个样板对象中复制一个内部属性一致的对象,这个过程也就是我们称的“克隆”。被复制的实例就是我们所称的“原型”,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

6.1、浅拷贝实现原型模式

新建Book类实现Cloneable接口,复写clone方法

//书原型(浅拷贝)
public class Book implements Cloneable {
    //书名
    private String title;
    //书页数
    private ArrayList<String> page = new ArrayList<String>();

    public Book() {
        //一个实现了Cloneable并重写了clone方法的类,通过clone得到对象构造函数是不会被执行
        System.out.println("构造函数被执行了");
    }

    @Override
    protected Book clone() {
        try {
            return (Book) super.clone();
        } catch (CloneNotSupportedException e) {
            //异常处理
            e.printStackTrace();
            return null;
        }
    }

    public String getTitle() {return title;}

    public void setTitle(String title) {this.title = title;}

    public List<String> getPage() {return page;}

    public void addPage(String page) {this.page.add(page);}

    @Override
    public String toString() {
        return "Book{" +"title='" + title + '\'' +", page=" + page +'}';
    }
}

测试原型模式

//浅拷贝测试
Book book = new Book();
book.setTitle("Java");
book.addPage("Java第1章");
System.out.println(book.toString());//Book{title='Java', page=[Java第1章]}

Book cloneBook = book.clone();
cloneBook.setTitle("Android");
cloneBook.addPage("Android第1章");
System.out.println(cloneBook.toString());//Book{title='Android', page=[Java第1章, Android第1章]}

// 再次打印原始书本发现原书信息被修改(因为Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、集合、容器对象、引用对象等都不会拷贝,需要使用深拷贝解决)
System.out.println(book.toString());//Book{title='Java', page=[Java第1章, Android第1章]}

6.2、深拷贝实现原型模式

新建Book类实现Cloneable接口,复写clone方法,因为Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、集合、容器对象、引用对象等都不会拷贝,需要将page进行深度拷贝.

//书原型(深拷贝)
public class Book2 implements Cloneable {
    //书名
    private String title;
    //书页数
    private ArrayList<String> page = new ArrayList<String>();

    @Override
    protected Book2 clone() {
        try {
            Book2 book2 = (Book2) super.clone();
            book2.page= (ArrayList<String>) this.page.clone();
            return book2;
        } catch (CloneNotSupportedException e) {
            //异常处理
            e.printStackTrace();
            return null;
        }
    }

    public String getTitle() {return title;}

    public void setTitle(String title) {this.title = title;}

    public List<String> getPage() {return page;}

    public void addPage(String page) {this.page.add(page);}

    @Override
    public String toString() {
        return "Book{" +"title='" + title + '\'' +", page=" + page +'}';
    }
}

测试原型模式

//深拷贝测试
Book2 book2 = new Book2();
book2.setTitle("Java");
book2.addPage("Java第1章");
System.out.println(book2.toString());//Book{title='Java', page=[Java第1章]}

Book2 cloneBook2 = book2.clone();
cloneBook2.setTitle("Android");
cloneBook2.addPage("Android第1章");
System.out.println(cloneBook2.toString());//Book{title='Android', page=[Java第1章, Android第1章]}

// 再次打印原始书本发现原书没有被改变
System.out.println(book2.toString());//Book{title='Java', page=[Java第1章]}

6.3、使用原型模式注意事项

  • 构造函数不会被执行

    通过clone复制得到对象,该对象构造函数是不会被执行的。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不 会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。在单例模式中,只要将构造方法的访问权限设置为 private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的。

  • 浅拷贝和深拷贝

    clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。在开发中,为减少错误,建议在使用原型模式时尽量使用深拷贝,避免操作副本时影响原始对象的问题。如果是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现。

  • clone与final

    要使用clone方法,类的成员变量上不要增加final关键字,因为final类型是不允许重赋值的。

七、建造者模式

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

7.1、建造者模式的定义

定义产品类

//具体产品类
public class Product {
    private String name;
    private String desc;
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public String getDesc() {return desc;}
    public void setDesc(String desc) {this.desc = desc;}
    @Override
    public String toString() {
        return "Product{" +"name='" + name + '\'' +", desc='" + desc + '\'' +'}';
    }
}

定义抽象建造者

//定义抽象建造者
public abstract class Builder {
    //设置产品名称
    abstract void setName(String name);
    //设置产品描述
    abstract void setDesc(String desc);
    //构建产品
    abstract Product builderProduct();
}

定义具体建造者

//定义具体建造者
public class ConcreteProduct extends Builder {
    private final Product product;
    public ConcreteProduct() { product = new Product();}
    @Override
    void setName(String name) {
        product.setName(name);
    }
    @Override
    void setDesc(String desc) {
        product.setDesc(desc);
    }
    @Override
    Product builderProduct() {
        return product;
    }
}

定义指挥者类

//定义指挥者类
public class Director {
    private final ConcreteProduct concreteProduct;
    public Director() {
        concreteProduct = new ConcreteProduct();
    }
    public Product getProduct(String name,String desc){
        concreteProduct.setName(name);
        concreteProduct.setDesc(desc);
       return concreteProduct.builderProduct();
    }
}

使用建造者模式

public static void main(String[] args) {
        Director director = new Director();
        Product product = director.getProduct("产品A", "A 的描述");
        System.out.println(product);//Product{name='产品A', desc='A 的描述'}
    }

7.2、Android中建造者实现

Android中的AlertDialog.Builder就是使用了Builder模式来构建AlertDialog的。

Android AlertDialog简单构建与使用

AlertDialog.Builder builder = new AlertDialog.Builder(activity);//创建一个Builder对象
  builder.setIcon(R.drawable.icon);
  builder.setTitle("标题");
  builder.setMessage("内容");
  builder.setPositiveButton("确定", null);
  AlertDialog alertDialog = builder.create();//创建AlertDialog对象
  alertDialog.show();//显示AlertDialog

通过Builder对象来构建Icon、Title、Message、按钮监听等,将AlertDialog的构建过程和细节隐藏了起来。

AlertDialog部分源码:

public class AlertDialog extends AppCompatDialog implements DialogInterface {
    final AlertController mAlert;
    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }
    ……
    public static class Builder {
        private final AlertController.AlertParams P;//存放构建时设置的参数
        ……
        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
		……
     	public Builder(@NonNull Context context) {//构建Builder
            this(context, resolveDialogTheme(context, 0));
        }
        ……
        public Builder setTitle(@Nullable CharSequence title) {//设置标题
            P.mTitle = title;
            return this;
        }
        ……
        public Builder setMessage(@Nullable CharSequence message) {//设置内容
            P.mMessage = message;
            return this;
        }
		……
        public Builder setIcon(@DrawableRes int iconId) {//设置icon
            P.mIconId = iconId;
            return this;
        }
		……
        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {//设置按钮和监听
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }
		……
        public AlertDialog create() {//构建AlertDialog
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);//将构建时参数通过AlertController运用到AlertDialog上
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {dialog.setCanceledOnTouchOutside(true);}
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {dialog.setOnKeyListener(P.mOnKeyListener);}
            return dialog;
        }
		……
    }
}

AlertController部分源码:

class AlertController { 
	public static class AlertParams {
  	……
        public void apply(AlertController dialog) {//将构建时参数通过AlertController运用到AlertDialog上
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
                if (mTitle != null) {
                    dialog.setTitle(mTitle);
                }
                if (mIcon != null) {
                    dialog.setIcon(mIcon);
                }
                if (mIconId != 0) {
                    dialog.setIcon(mIconId);
                }
                if (mIconAttrId != 0) {
                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
                }
            }
            if (mMessage != null) {
                dialog.setMessage(mMessage);
            }
            if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                        mPositiveButtonListener, null, mPositiveButtonIcon);
            }
            ……

Android中 AlertDialog使用是通过AlertDialog.Builder设置各种属性后(如:setTitle()),这些属性信息会保存在P(AlertController.AlertParams)变量中,调用create()即可返回一个AlertDialog对象,create()方法中P.apply(dialog.mAlert)是将构建时设置的参数通过AlertController运用到AlertDialog上,最后调用AlertDialog中的show()显示对话框。AlertDialog通过builder模式隐藏了这种复杂的构建过程,只需几行简单的代码就把AlertDialog给展示出来了,AlertDialogbuilder中并没有抽象建造者(Builder)、Director(指挥者类)等角色。AlertDialog.Builder同时扮演了BuilderConcreteBuilderDirector等角色,这是Android源码中使用构建者模式的一种简化。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值