创建型设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

有三个分类:

  1. 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
  2. 结构型模式:把类或对象结合在一起形成一个更大的结构。
  3. 行为型模式:类和对象如何交互,及划分责任和算法。

注:所有图片来源于网络,如有侵权立刻删除。

单例模式

定义

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

组成

单例(Singleton)

单例模式类图

代码模型

//双重检查模式(DCL)
public class Singleton {  
      private volatile static Singleton singleton;  
      private Singleton (){
      }   
      public static Singleton getInstance() {  
      if (instance== null) {  
          synchronized (Singleton.class) {  
          if (instance== null) {  
              instance= new Singleton();  
          }  
         }  
     }  
     return singleton;  
     }  
 }  
 
 
 //静态内部类
 public class Singleton { 
    private Singleton(){
    }
      public static Singleton getInstance(){  
        return SingletonHolder.sInstance;  
    }  
    private static class SingletonHolder {  
        private static final Singleton sInstance = new Singleton();  
    }  
 }
 
 //容器类实现
 public class SingletonManager {
    private static Map<String,Object> objMap = new HashMap<String,Object>();

    private SingletonManager(){}

    public static void registerService(String key,Object instance){
        if (!objMap.containsKey(key)){
            objMap.put(key,instance);
        }
    }

    public static Object getInstance(String key){
        return objMap.get(key);
    }

  }

优点

  1. 提供了对唯一实例的受控访问
  2. 仅存在一个实例对象,节约系统资源

缺点

  1. 单例模式无抽象层,不易拓展
  2. 单例模式的职责过重,违背了单一职责原则

适用范围

系统只需要一个实例对象,调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

使用实例

通过Context获取系统级别的服务时,这些系统服务其实就是单例模式实现的,使用了容器实现的单例模式。

    //使用LayoutInflater加载布局时
    LayoutInflater.from(mContext).inflate(mLayoutId, null);

    public static LayoutInflater from(Context context) {
        //获取系统服务
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
    
    

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    
    //SYSTEM_SERVICE_FETCHERS为键值对容器,用来存储生成的SystemService
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    
    
    //SYSTEM_SERVICE_FETCHERS的初始化
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();
        
    //LayoutInflater的创建加入在一个静态的代码块中进行注册服务,第一次加载该类的时候执行,并且只执行一次,保证实例的唯一性
    static {
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                     return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
    }

建造者模式

定义

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

组成

  1. Builder:抽象建造者,该类同样为抽象类,规范Product的组建,一般由子类实现具体Product的构建过程;
  2. ConcreteBuilder:具体建造者,继承自Builder,构建具体的Product;
  3. Director:指挥者,统一组装过程,作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程;
  4. Product:产品角色,该类为一般为抽象类,定义Product的公共属性配置;
建造者模式类图

代码模型

public class Director {
    private Builder builder;

    // 构造产品
    public void construct() {
        builder = new ConcreteBuilder();
        builder.buildPart1();
        builder.buildPart2();
    }
}

abstract public class Builder {
    // 构造"产品的Part1"的接口
    public abstract void buildPart1();
    // 构造"产品的Part2"的接口
    public abstract void buildPart2();
    // 返回产品
    public abstract Product retrieveResult();
}

public class ConcreteBuilder {
    private Product product = new Product();
    public void buildPart1() {
        // 参见产品的Part1
    }
    public void buildPart2() {
        // 参见产品的Part2
    }
    public Product retrieveResult() {
        return product;
    }
}

public class Product {
     // 产品组成部分...
}

优点

  1. 封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节
  2. 建造者独立,容易扩展
  3. 可以更加精细地控制产品的创建过程

缺点

如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

适用范围

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  3. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

使用实例

Retrofit.Builder就是一个简化版的建造者模式,Retrofit直接对应Product,并没有基于抽象Product进行扩展;Retrofit.Builder对应ConcreteBuilder,也没有基于抽象Builder进行扩展,同时省略了Director,并在Retrofit.Builder每个setter方法都返回自身,使得客户端代码可以链式调用,整个构建过程更加简单。

Retrofit retrofitBuilder = new Retrofit.Builder()
                .baseUrl(NetworkConfig.getNewBaseUrl())
                .client(mOkHttpClient)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(CustomGsonConverterFactory.create())
                .build();

工厂模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

组成

  1. 抽象工厂(Factory)
  2. 具体工厂(ConcreteFactory)
  3. 抽象产品(Product)
  4. 具体产品(ConcreteProduct)。
工厂模式类图

代码模型

//抽象工厂:提供“创建产品”的函数接口,并由其子类实现。
public interface Factory {
    public Product newInstance() ;
}
//具体工厂:实现了抽象工厂提供的“创建产品”接口。
public class ConcreteFactory1 implements Factory {
    public Product newInstance() {
        return new ConcreteProduct1();
    }
}
public class ConcreteFactory2 implements Factory {
    public Product newInstance() {
        return new ConcreteProduct2();
    }
}
//抽象产品:提供了产品的公用方法函数
public interface Product {
}
//具体产品:实现了抽象产品的函数接口。
public class ConcreteProduct1 implements Product {
    public ConcreteProduct1() {
        // do something
    }
}
public class ConcreteProduct2 implements Product {
    public ConcreteProduct2() {
        // do something
    }
}

优点

  1. 封装性良好,代码结构清晰。
  2. 拓展性优秀。
  3. 屏蔽产品类,客户无需关心创建细节,仅需关心对应工厂。
  4. 典型的解耦框架,符合单一职责原则(具体的工厂只负责对应的产品),里氏替换原则(子类能覆盖父类对象)

缺点

  1. 新增产品时,需要新增对应的具体工厂类,增加了系统的复杂度。
  2. 为了拓展性引入了抽象层,增加了系统的理解难度。

适用环境

需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。

使用实例

Retrofit的CallAdapter创建使用了工厂模式。

//CallAdapter<T> 为抽象产品
public interface CallAdapter<T> {

    Type responseType();
    <R> T adapt(Call<R> call);
 
    //allAdapter.Factory为抽象工厂类
    abstract class Factory {
        public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
            return Utils.getParameterUpperBound(index, type);
        }

        protected static Class<?> getRawType(Type type) {
            return Utils.getRawType(type);
        }
    }
}

而它的具体工厂的实现类,已RxJava2CallAdapterFactory为例

public final class RxJavaCallAdapterFactory extends CallAdapter.Factory {

    // 省略代码
    @Override
    public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    
        // 省略代码
        
        //此处的RxJava2CallAdapter即为具体产品类
        return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,
        isSingle, isMaybe, false);
    }

    private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
        // 省略代码
    }
}

抽象工厂模式

定义

为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。

产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

组成

  1. 抽象工厂(Factory)
  2. 具体工厂(ConcreteFactory)
  3. 抽象产品(Product)
  4. 具体产品(ConcreteProduct)。

抽象工厂模式类图

抽象工厂模式与工厂方法模式的区别:
在工厂方法模式中,"抽象产品"只有一个,而在抽象工厂模式中,"抽象产品"有很多个!在工厂方法模式中,是由"具体工厂"决定返回哪一类产品;然后,抽象工厂中,是由"客户端"决定返回哪一类产品。

代码模型

public interface Factory {
    public ProductA newProductA();
    public ProductB newProductB();
}
public class ConcreteFactory1 implements Factory {
    public ProductA newProductA() {
        return new ConcreteProductA1();
    }
    public ProductB newProductB();
        return new ConcreteProductB1();
    }
}
public class ConcreteFactory2 implements Factory {
    public ProductA newProductA2() {
        return new ConcreteProduct1();
    }
    public ProductB newProductB2();
        return new ConcreteProduct1();
    }
}
public interface ProductA {
}
public class ProductA1 implements ProductA {
}
public class ProductA2 implements ProductA {
}
public interface ProductB {
}
public class ProductB1 implements ProductB {
}
public class ProductB2 implements ProductB {
}

优点

  1. 封装性良好,代码结构清晰。
  2. 拓展性优秀。增加新的具体工厂和产品族很方便。
  3. 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。

缺点

  1. 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
  2. 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。

适用环境

一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。
例如一个文本编辑器和一个图片处理器,都是软件实体,但是Unix下的文本编辑器和Windows下的文本编辑器虽然功能和界面都相同,但是代码实现是不同的,图片处理器也有类似情况。也就是具有了共同的约束条件:操作系统类型。于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器

使用实例

Retrofit中CallAdapter的使用的设计模式即为抽象工厂模式

public interface Converter<F, T> {
    T convert(F value) throws IOException;

    //抽象工厂
    abstract class Factory {
        //Converter<ResponseBody, ?>为抽象产品1
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, 
               Retrofit retrofit) {
            return null;
        }

        //Converter<?, RequestBody>为抽象产品2
        public Converter<?, RequestBody> requestBodyConverter(Type type, 
               Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            return null;
        }
        
        //省略部分代码
    }
}

这里以GsonConverterFactory为例子

//具体工厂
public final class GsonConverterFactory extends Converter.Factory {
    // 省略代码
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        //GsonResponseBodyConverter<>为具体产品1
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        //GsonRequestBodyConverter<>为具体产品2
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}

简单工厂模式

定义

又被称为"静态工厂方法模式"。它属于"创建模式"(创建对象的模式),并且是"工厂方法"模式的一种特殊实现。

组成

  1. 工厂(Factory)
  2. 抽象产品(Product)
  3. 具体产品(ConcreteProduct)。

简单工厂模式类图

工厂模式与简单工厂模式的区别
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

代码模型

public class Factory {
    public static Product newInstance() {
        return new ConcreteProduct();
    }
}
public abstract Product {
}
public class ConcreteProduct extends Product {
    public ConcreteProduct() {}
}

优点

  1. 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  2. 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可。

缺点

  1. 工厂类的扩展比较困难,不符合开闭原则
  2. 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响

适用环境

工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。

应用实例

Retrofit 中的Platform运用了简单工厂模式

//抽象产品
class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  //工厂
  private static Platform findPlatform() {
    try {
      Class.forName("android.os.Build");
      if (Build.VERSION.SDK_INT != 0) {
        //具体产品1
        return new Android();
      }
    } catch (ClassNotFoundException ignored) {
    }
    try {
      Class.forName("java.util.Optional");
      //具体产品2
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }
  //省略部分代码
}

原型模式

定义

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

组成

  1. 客户(client),客户类提出创建对象的请求。
  2. 抽象原型(PhotoType),抽象原型给出所有的具体原型所需要实现的函数接口。
  3. 具体原型(ConcretePrototype),被复制的对象,实现了抽象原型的接口。
原型模式类图

代码模型

public interface Prototype extends Cloneable {
    Prototype clone();
}
public class ConcretePrototype implements Prototype {
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}
public class Client {
    private Prototype prototype;
    public void operation(Prototype example) {
        Prototype p = (Prototype) example.clone();
    }
}

优点

性能优良,内存二进制的拷贝,比直接new一个对象性能好很多。

缺点

构造函数不会执行,缺少相应约束

适用范围

  1. 资源优化场景
  2. 一个对象多个修改者的场景

使用实例

Retrofit中的用于发起网络请求的OkHttpCall就使用了原型模式,优势在于使用原型模式的二进制的copy性能较好,当由大量网络请求时,该优势就能更明显的体现出来了。

final class OkHttpCall<T> implements Call<T> {
    //省略部分代码
    @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
    @Override public OkHttpCall<T> clone() {
    return new OkHttpCall<>(serviceMethod, args);
  }
    //省略部分代码
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容