查看原文
其他

从零开始学设计模式(二)--抽象工厂模式,单例模式,建造者模式及原型模式

小暴说 云时代架构 2019-05-09
    选择“置顶公众号”,精品文章第一时间送达!


上一篇:从零开始学设计模式(一)——设计模式介绍及工厂模式


今天继续学习从零开始学设计模式,包含:抽象工厂模式(Abstract Factory),单例模式(Singleton Pattern),建造者模式(Builder Pattern),原型模式(Prototype Pattern)


抽象工厂模式(Abstract Factory)

抽象工厂模式可以说是对简单工厂模式的一种延伸,它是围绕一个超级工厂来创建其他简单工厂,该超级工厂又称为其他工厂的工厂。


这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。


意图


提供一个接口,用于创建一系列相关或有依赖关系的对象,而无需指定它们的具体类。

主要解决:主要解决接口选择的问题。

何时使用:系统的产品有多于一个的产品族类,而系统只消费其中某一族类的产品。

如何解决:在一个产品族里面,定义多个产品。

关键代码:在一个工厂里聚合多个同类产品。


解释


现实世界的例子


要创建一个王国,我们需要有共同主题的物体。精灵王国需要精灵国王、精灵城堡和精灵军队,而兽人王国需要兽人国王、兽人城堡和兽人军队。王国中的对象之间存在依赖关系


简而言之


工厂的工厂;将个别但相关/从属工厂组合在一起而不指定其具体类别的工厂。


维基百科


The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes(抽象工厂模式提供了一种封装一组具有共同主题的独立工厂的方法,而无需指定它们的具体类)


程序代码实现


以上面现实世界的王国例子。下面的类图展示了不同具体的工厂和它们生产的不同具体产品,精灵王国的工厂生产精灵城堡和精灵军队,兽人王国的工厂生产兽人城堡和兽人军队。

diagram_kingdom1.png


按照上面的类图,我们首先来定义一些接口和实现类在王国中:

public interface Castle {
 String getDescription();
}
public interface King {
 String getDescription();
}
public interface Army {
 String getDescription();
}

// Elven implementations ->
public class ElfCastle implements Castle {
 static final String DESCRIPTION = "This is the Elven castle!";
 @Override
 public String getDescription() {
   return DESCRIPTION;
 }
}
public class ElfKing implements King {
 static final String DESCRIPTION = "This is the Elven king!";
 @Override
 public String getDescription() {
   return DESCRIPTION;
 }
}
public class ElfArmy implements Army {
 static final String DESCRIPTION = "This is the Elven Army!";
 @Override
 public String getDescription() {
   return DESCRIPTION;
 }
}

// Orcish implementations similarly... 兽人实现与上面类似


接下来我们再来定义抽象的王国工厂接口和他的具体实现工厂

public interface KingdomFactory {
 Castle createCastle();
 King createKing();
 Army createArmy();
}

public class ElfKingdomFactory implements KingdomFactory {
 public Castle createCastle() {
   return new ElfCastle();
 }
 public King createKing() {
   return new ElfKing();
 }
 public Army createArmy() {
   return new ElfArmy();
 }
}

public class OrcKingdomFactory implements KingdomFactory {
 public Castle createCastle() {
   return new OrcCastle();
 }
 public King createKing() {
   return new OrcKing();
 }
 public Army createArmy() {
   return new OrcArmy();
 }
}


现在我们有了我们的抽象工厂,让我们制作相关联类别的家族对象,例如精灵王国工厂创建精灵城堡、精灵国王和精灵军队等

KingdomFactory factory = new ElfKingdomFactory();
Castle castle = factory.createCastle();
King king = factory.createKing();
Army army = factory.createArmy();

castle.getDescription();  // Output: This is the Elven castle!
king.getDescription(); // Output: This is the Elven king!
army.getDescription(); // Output: This is the Elven Army!


最后我们再设计一个超级工厂来生产不同种类的王国工厂。在本例中我们创建一个FactoryMaker来生产ElfKingdomFactory or OrcKingdomFactory


客户端可以通过FactoryMaker类来创建期望的具体王国工厂,具体的工厂将会生产具体的对象(军队、国王、城堡)


同样在本例中我们客户端也使用枚举作为参数来指明我们想创建具体哪一类的工厂。这一块UML关系class类图如下:

diagram_kingdom2


部分重要代码如下:

public static class FactoryMaker {

 public enum KingdomType {
   ELF, ORC
 }

 public static KingdomFactory makeFactory(KingdomType type) {
   switch (type) {
     case ELF:
       return new ElfKingdomFactory();
     case ORC:
       return new OrcKingdomFactory();
     default:
       throw new IllegalArgumentException("KingdomType not supported.");
   }
 }
}

public static void main(String[] args) {
 
   App app = new App();

   LOGGER.info("Elf Kingdom");
   app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF));
   LOGGER.info(app.getArmy().getDescription());
   LOGGER.info(app.getCastle().getDescription());
   LOGGER.info(app.getKing().getDescription());

   LOGGER.info("Orc Kingdom");
   app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC));
   LOGGER.info(app.getArmy().getDescription());
   LOGGER.info(app.getCastle().getDescription());
   LOGGER.info(app.getKing().getDescription());
 }


运行程序app输出:

16:06:12.289 [main] INFO com.iluwatar.abstractfactory.App - Elf Kingdom
16:06:12.295 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven Army!
16:06:12.295 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven castle!
16:06:12.295 [main] INFO com.iluwatar.abstractfactory.App - This is the Elven king!
16:06:12.296 [main] INFO com.iluwatar.abstractfactory.App - Orc Kingdom
16:06:12.297 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc Army!
16:06:12.297 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc castle!
16:06:12.297 [main] INFO com.iluwatar.abstractfactory.App - This is the Orc king!


应用场景


当遇到如下情形时,你应该考虑使用抽象工厂模式:

  1. 一个系统应该独立于它的产品是如何创建、组成和表现的

  2. 系统应该配置有多个产品系列之一

  3. 相关产品对象系列被设计为一起使用,你需要强制执行此约束

  4. 你想提供一个产品类库,你想展示的只是他们的接口,而不是他们的实现

  5. 依赖项的生命周期在概念上比消费者的生命周期短

  6. 你需要一个运行时值来构造一个特定的依赖关系

  7. 你想决定在运行时从一个家族中调用哪个产品

  8. 你需要通过提供一个或多个仅在运行时已知的参数,就能来解析依赖项


使用场景


  • 应用在运行时需要选择调用适当的实现如FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService

  • 单元测试用例编写变的更加简单


Java中的现实例子


  • javax.xml.parsers.DocumentBuilderFactory

  • javax.xml.transform.TransformerFactory

  • javax.xml.xpath.XPathFactory


优缺点


优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。


缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码


写在最后


抽象工厂模式是工厂模式的超级聚合,当产品只有一个的时候,抽象工厂模式也就变成了工厂模式,当工厂模式的产品变成多个时,工厂模式也就变成了抽象工厂模式。


抽象工厂的模式比工厂模式要复杂很多,这也就导致系统在扩展一个工厂或者一个产品时,需要改动的地方会很多,程序耦合性较高。


以上面王国的例子,假如我们要新增一个人类王国,那么我们需要新增人类王国工厂,人类城堡、人类军队,UML类图如下:


diagram_humanKingdom.png


同样再想想,假如我们要新增一个产品的时候,如一个王国除了国王、城堡、军队之外还应该有瞭望塔,那么在上图的基础上我们需要修改和新增哪些接口和类呢?读者可以自己思考一下,试着画一下UML图。


单例模式(Singleton Pattern)


单例模式也属于创建型模式,难度等级为初级,是Java中最简单和最常见的设计模式之一。由于其常见性,单例模式的实现方法衍生出很多种,不同的实现方式在延迟加载、线程安全、性能上各有千秋,后面我们会在程序代码说明章节中来具体分析。


单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。


意图


确保一个类只有一个实例,并提供对它的全局访问点

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当你想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。


解释


现实世界的例子

只有一座象牙塔可以让巫师们研究他们的魔法。巫师们总是使用同样的魔法象牙塔。这里的象牙塔是单例


简而言之

确保一个特定类永远只创建一个对象实例


维基百科

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.(在软件工程中,singleton模式是一种软件设计模式,它将类的实例化限制为一个对象。当只需要一个对象来协调整个系统的动作时,这很有用)


程序代码说明


1、懒汉式,线程不安全的

public class Singleton{
   private static Singleton instance;  
   private Singleton (){}  
 
   public static Singleton getInstance() {  
   if (instance == null) {  
       instance = new Singleton();  
   }  
   return instance;  
   }  
}

这种方式是最基本的实现方式,但由于其不支持多线程,getInstance方法没有加锁,并发的情况下可能产生多个instance实例,所以严格意义上来说它并不算单例模式,但它做到了Lazy初始化。

这种方式目前已不建议使用


2、懒汉式,线程安全的
以上面巫师们的象牙塔为例子

/**
* Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization
* mechanism.
*
* Note: if created by reflection then a singleton will not be created but multiple options in the
* same classloader
*/

public final class ThreadSafeLazyLoadedIvoryTower {

 private static ThreadSafeLazyLoadedIvoryTower instance;

 private ThreadSafeLazyLoadedIvoryTower() {
   // protect against instantiation via reflection
   if (instance == null) {
     instance = this;
   } else {
     throw new IllegalStateException("Already initialized.");
   }
 }

 /**
  * The instance gets created only when it is called for first time. Lazy-loading
  */

 public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
   if (instance == null) {
     instance = new ThreadSafeLazyLoadedIvoryTower();
   }

   return instance;
 }
}

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是由于加了同步锁,效率很低,99%情况下不需要同步。对于getInstance()的性能要求不是很高的应用程序可以考虑使用(该方法使用不频繁)


3、双检锁/双重校验锁(DCL,即 double-checked locking)

此方式需在JDK1.4之后,采用双锁机制,安全且在多线程情况下保持了较高的性能。

public final class ThreadSafeDoubleCheckLocking {

 private static volatile ThreadSafeDoubleCheckLocking instance;

 /**
  * private constructor to prevent client from instantiating.
  */

 private ThreadSafeDoubleCheckLocking() {
   // to prevent instantiating by Reflection call
   if (instance != null) {
     throw new IllegalStateException("Already initialized.");
   }
 }

 /**
  * Public accessor.
  *
  * @return an instance of the class.
  */

 public static ThreadSafeDoubleCheckLocking getInstance() {
   // local variable increases performance by 25 percent
   // Joshua Bloch "Effective Java, Second Edition", p. 283-284
   
   ThreadSafeDoubleCheckLocking result = instance;
   // Check if singleton instance is initialized. If it is initialized then we can return the instance.
   if (result == null) {
     // It is not initialized but we cannot be sure because some other thread might have initialized it
     // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion.
     synchronized (ThreadSafeDoubleCheckLocking.class) {
       // Again assign the instance to local variable to check if it was initialized by some other thread
       // while current thread was blocked to enter the locked zone. If it was initialized then we can
       // return the previously created instance just like the previous null check.
       result = instance;
       if (result == null) {
         // The instance is still not initialized so we can safely (no other thread can enter this zone)
         // create an instance and make it our singleton instance.
         instance = result = new ThreadSafeDoubleCheckLocking();
       }
     }
   }
   return result;
 }
}


4、饿汉式

这种方式比较常用,但容易产生垃圾对象,没有锁机制,执行效率很高,但未实现Lazy初始化。

**
* Singleton class. Eagerly initialized static instance guarantees thread safety.
*/
public final class IvoryTower
{

 /**
  * Private constructor so nobody can instantiate the class.
  */

 private IvoryTower() {}

 /**
  * Static to class instance of the class.
  */

 private static final IvoryTower INSTANCE = new IvoryTower();

 /**
  * To be called by user to obtain instance of the class.
  *
  * @return instance of the singleton.
  */

 public static IvoryTower getInstance() {
   return INSTANCE;
 }
}

基于classloader机制避免了多线程的同步问题,不过由于IvoryTower类在装载时就已经实例化了,显然没有达到lazy loading的效果。


5、登记式/静态内部类

这种方式可以达到上面第三种双检锁一样的功效,具备了延迟加载和线程安全,且实现简单。

对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

public final class InitializingOnDemandHolderIdiom {

 /**
  * Private constructor.
  */

 private InitializingOnDemandHolderIdiom() {}

 /**
  * @return Singleton instance
  */

 public static InitializingOnDemandHolderIdiom getInstance() {
   return HelperHolder.INSTANCE;
 }

 /**
  * Provides the lazy-loaded Singleton instance.
  */

 private static class HelperHolder {
   private static final InitializingOnDemandHolderIdiom INSTANCE =
       new InitializingOnDemandHolderIdiom();
 }
}

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 4 种方式不同的是:第 4种方式只要 IvoryTower 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 InitializingOnDemandHolderIdiom 类被装载了,instance 不一定被初始化。因为 HelperHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 HelperHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以我们想让它延迟加载,这种方式相比第 4 种方式就显得更合理。

此方式对JDK版本也无任何要求,在现在能看到的JDK版本下都支持。


6、枚举

Joshua Bloch, Effective Java 2nd Edition p.18 中提出了使用枚举类来实现单例模式,书中指出这是最好的一种方式来实现单例。


A single-element enum type is the best way to implement a singleton


/**
* Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18
*
* This implementation is thread safe, however adding any other method and its thread safety
* is developers responsibility.
*/

public enum EnumIvoryTower {

 INSTANCE;

 public void whateverMethod() {
      // .... do something
 }
 @Override
 public String toString()
{
   return getDeclaringClass().getCanonicalName() + "@" + hashCode();
 }
}


然后我们使用它

EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
assertEquals(enumIvoryTower1, enumIvoryTower2); // true

总结一下:一般情况下,方式1和2不建议使用,在明确不需要实现lazy loading的时候可以优先选择方式4.在需要lazy loading的时候,可以选择方式3和5。如果还涉及到反序列化创建对象时可以使用最佳方式6


应用场景


当遇到如下情况时可考虑使用单例模式:

  • 一个类必须只有一个实例,客户端必须可以从众所周知的通道访问它

  • 一个单实例应该可以被子类可扩展,并且客户端能够使用扩展实例而无需修改其代码


使用场景


  • 日志记录类

  • 管理一个数据库连接

  • 文件管理器


Java中的现实例子


  • java.lang.Runtime#getRuntime()

  • java.awt.Desktop#getDesktop()

  • java.lang.System#getSecurityManager()


优缺点


优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。


缺点:
1、没有接口,不能继承,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2、通过控制自己的创建和生命周期,违反了单一责任原则SRP(Single Responsibility Principle)
3、创建紧密耦合的代码,单例模式的客户端变得难以测试


写在最后


单例模式的使用在Java应用中是非常普遍的,Spring 管理bean 实例默认配置就是单例模式。


建造者模式(Builder Pattern)


建造者模式使用多个简单的对象一步一步构建成一个复杂的对象,这种类型的设计模式也属于创建型模式,它提供了一种创建对象的最佳方式。


一个Builder 类会一步一步构造最终的对象。该Builder 类是独立于其他对象的


意图


将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示

主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

解释


现实世界的例子

想象一个角色扮演游戏的角色生成器。最简单的选择是让电脑为你创建角色。但是如果你想选择角色细节,如职业、性别、发色等。当你所有选择一步一步都选定好时,角色生成器也就逐步生成了一个角色,这一过程就是建造者模式创建对象的过程


简而言之

允许你创建不同风格的对象,同时避免构造函数污染。当一个对象可能有多种风格时很有用。或者当创建对象涉及很多步骤时


维基百科说

The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern(建造者模式是一种对象创建软件设计模式,旨在找到伸缩构造器反模式的解决方案)


说到这里,让我补充一下伸缩构造函数反模式是什么。在这一点或其他方面,我们可能都见过类似下面这样的构造函数

public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
}

正如你可以看到的未来那样,这个构造函数参数的数量会很快失控,并且很难理解参数的排列顺序和组合。此外,如果你在未来增加更多选项,这个参数列表可能会继续增长。这称为伸缩构造函数反模式


程序代码示例


上面的N参数构造函数示例,明智的选择是使用建造者模式。首先,我们有我们想要创造的英雄

public final class Hero {
 private final Profession profession;
 private final String name;
 private final HairType hairType;
 private final HairColor hairColor;
 private final Armor armor;
 private final Weapon weapon;

 private Hero(Builder builder) {
   this.profession = builder.profession;
   this.name = builder.name;
   this.hairColor = builder.hairColor;
   this.hairType = builder.hairType;
   this.weapon = builder.weapon;
   this.armor = builder.armor;
 }
}


然后我们设计建造者

public static class Builder {
   private final Profession profession;
   private final String name;
   private HairType hairType;
   private HairColor hairColor;
   private Armor armor;
   private Weapon weapon;

   public Builder(Profession profession, String name) {
     if (profession == null || name == null) {
       throw new IllegalArgumentException("profession and name can not be null");
     }
     this.profession = profession;
     this.name = name;
   }

   public Builder withHairType(HairType hairType) {
     this.hairType = hairType;
     return this;
   }

   public Builder withHairColor(HairColor hairColor) {
     this.hairColor = hairColor;
     return this;
   }

   public Builder withArmor(Armor armor) {
     this.armor = armor;
     return this;
   }

   public Builder withWeapon(Weapon weapon) {
     this.weapon = weapon;
     return this;
   }

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


最后我们可以这样来建造Hero:

Hero mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build();


应用场景


当遇到如下的情况你应该考虑使用建造者模式:

  • 创建复杂对象的算法应该独立于组成对象的部件以及它们是如何组装的

  • 构建过程必须允许对构建的对象进行不同的表示


Java中的现实例子

  • java.lang.StringBuilder

  • java.nio.ByteBuffer 还有其他类似的buffers 比如 FloatBuffer, IntBuffer 等等.

  • java.lang.StringBuffer

  • All implementations of java.lang.Appendable

  • Apache Camel builders


优缺点


优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。


写在最后


建造者模式又称之为生成器模式,一般来说有三个角色:建造者、具体的建造者、监工角色,为了形象的说明这三个角色的结构和定义我们自己来设计一个程序实例。


我们假设要制作一份宣传文案,一份文案可以包含一个或多个文档,文档有三种类型:文字文档、图表文档、图片文档。根据不同类型文档的组合我们有不同类型的文案生成,如文字文案由纯文字文档组成,图表文案由图表文档和图片文档组成,混合文案由文字文档、图表文档、图片文档三者共同组成。


不同类型的文档由不同的书写工具书写,如文字文档由MicrosoftWord工具编写,图表文档由MicrosoftExcel工具编写,图片文档由PhotoShop工具编写。

按照上面的假设需求,我们首先设计程序类图如下:


image.png


接下来编写程序
步骤一:创建文档接口和编写工具接口

public interface Document {

   /**
    *
    * @return 文档名称
    */

   String name();

   /**
    *
    * @return 文档类型
    */

   String type();

   /**
    *
    * @return 书写工具
    */

   WriteTool writeTool();
}
public interface WriteTool {

   /**
    *
    * @return 返回书写工具[名称]+"write"
    */

   String write();
}


步骤二:编写WriteTool接口的实现类

public class MicrosoftWord implements WriteTool{
   @Override
   public String write() {
       return "MicrosoftWord write";
   }
}

public class MicrosoftExcel implements WriteTool {
   @Override
   public String write() {
       return "MicrosoftExcel write";
   }
}
public class PhotoShop implements WriteTool{
   @Override
   public String write() {
       return "PhotoShop write";
   }
}


步骤三:编写Document接口的实现类

public class Chart implements Document {
   @Override
   public String name() {
       return "chart document";
   }

   @Override
   public String type() {
       return "table";
   }

   @Override
   public WriteTool writeTool() {
       return new MicrosoftExcel();
   }
}

public class Image implements Document {
   @Override
   public String name() {
       return "image document";
   }

   @Override
   public String type() {
       return "image";
   }

   @Override
   public WriteTool writeTool() {
       return new PhotoShop();
   }
}

public class Word  implements Document{
   @Override
   public String name() {
       return "word document";
   }

   @Override
   public String type() {
       return "text";
   }

   @Override
   public WriteTool writeTool() {
       return new MicrosoftWord();
   }
}


步骤四:编写建造者CopyWriter类

/**
* 不同的文案包含一些不同类型的文档
* 定义建造对象的方式方法
*/

public class CopyWriter {

   //包含的文档
   private  List<Document> documents = new ArrayList<>();

   //名字
   private String name;

   //文案类型  文字 图表 混合
   private String type;


   public CopyWriter(String name,String type){

       this.name = name;
       this.type = type;
   }

   //添加文档
   public CopyWriter addDocument(Document document) {

       if (null == document){
           throw new IllegalArgumentException("documnet can not be null");
       }
       this.documents.add(document);
       return this;
   }

   public String name(){
       return this.name;
   }

   public String getType(){
       return this.type;
   }
   //展示文案包含的文档信息
   public void showDocuments(){

       for (Document doc:documents)
       {
             System.out.print("name:"+doc.name());
             System.out.print(" type:"+doc.type());
             System.out.println(" writeTool:"+doc.writeTool().write());
       }
   }

}


步骤五:编写监工CopyWriterBuilder

//将一个复杂对象的构建过程与其表示相分离
public class CopyWriterBuilder {

   /**
    * 准备文本类型的文案
    * @return
    */

   public CopyWriter prepareTextCopyWriter(){

       CopyWriter copyWriter = new CopyWriter("TextCopyWriter","text");

       //文本类型的文案只需要准备文字文档即可
       copyWriter.addDocument(new Word());

       return copyWriter;
   }

   /**
    * 准备图表类型的文案
    * @return
    */

   public CopyWriter prepareTableCopyWriter(){

       CopyWriter copyWriter = new CopyWriter("TableCopyWriter","table");

       //图表类型的文案需要准备图表文档和图片文档
       copyWriter.addDocument(new Chart()).addDocument(new Image());
       return copyWriter;
   }

   /**
    * 准备混合类型的文案 包含文本和图表
    * @return
    */

   public CopyWriter prepareMixCopyWriter(){

       CopyWriter copyWriter = new CopyWriter("MixCopyWriter","Mix");

       //图表类型的文案需要准备图表文档、图片文档、文字文档
       copyWriter.addDocument(new Chart()).addDocument(new Image()).addDocument(new Word());
       return copyWriter;
   }

}


步骤六:最后编写使用者

public class App {

   public static void main(String[] args){

       CopyWriterBuilder builder = new CopyWriterBuilder();

       CopyWriter txtCopyWriter = builder.prepareTextCopyWriter();
       System.out.println(txtCopyWriter.name());
       txtCopyWriter.showDocuments();
       System.out.println("---------------------------------");
       CopyWriter tableCopyWriter = builder.prepareTableCopyWriter();
       System.out.println(tableCopyWriter.name());
       tableCopyWriter.showDocuments();
       System.out.println("---------------------------------");
       CopyWriter mixCopyWriter = builder.prepareMixCopyWriter();
       System.out.println(mixCopyWriter.name());
       mixCopyWriter.showDocuments();

   }

}


运行App输出结果如下:

TextCopyWriter
name:word document type:text writeTool:MicrosoftWord write
---------------------------------
TableCopyWriter
name:chart document type:table writeTool:MicrosoftExcel write
name:image document type:image writeTool:PhotoShop write
---------------------------------
MixCopyWriter
name:chart document type:table writeTool:MicrosoftExcel write
name:image document type:image writeTool:PhotoShop write
name:word document type:text writeTool:MicrosoftWord write


原型模式(Prototype Pattern)


原型模式是用于创建重复的对象,提高性能。这种模式实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。


例如一个对象需要在一个高代价的数据库操作或者远程连接之后被创建,我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库或者通知远程连接,以此来减少数据库或远程连接的调用。


意图


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

主要解决:在运行期建立和删除原型。

何时使用:
1、当一个系统应该独立于它的产品创建,构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码:
1、实现克隆操作,在 JAVA 中实现 Cloneable接口,重写 clone()方法。在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。


解释


现实世界的例子

记得多莉吗?克隆的羊!让我们不要谈细节,这里的关键点全是克隆


简而言之

通过克隆基于现有对象来创建新对象


维基百科

原型模式是软件开发中一种创造性的设计模式。当要创建的对象类型由一个原型实例确定并使用时,该原型实例被克隆以产生新的对象。


简而言之,它允许你创建一个现有对象的副本,并根据你的需要修改它,而不是从头开始创建一个对象并设置它


程序示例


在Java中,可以通过实现java.lang.Cloneable接口并重写clone方法来轻易的做到克隆对象

class Sheep implements Cloneable {
 private String name;
 public Sheep(String name) { this.name = name; }
 public void setName(String name) { this.name = name; }
 public String getName() { return name; }
 @Override
 public Sheep clone() throws CloneNotSupportedException {
   return new Sheep(name);
 }
}


然后上面这个Sheep类对象可以被克隆如下:

Sheep original = new Sheep("Jolly");
System.out.println(original.getName()); // Jolly

// Clone and modify what is required
Sheep cloned = original.clone();
cloned.setName("Dolly");
System.out.println(cloned.getName()); // Dolly


应用场景


1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用


Java中的现实例子


  • java.lang.Object#clone()


写在最后


注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。

浅拷贝实现 Cloneable接口,重写clone方法,深拷贝是通过实现 Serializable接口 读取二进制流来实现


在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,我们可以随手拿来使用。


接下来我们就来编写一个原型模式与工厂方法模式相结合的程序代码示例。

我们有一个HeroFactory工厂,这个工厂可以生产不同类型的(兽族、精灵族)Mage(魔法师)、Warlord(军队)、Beast(兽王),只是这些类型的产物在工厂中都不是直接New出来的,而是采用原型模式通过已有类型对象clone出来的。

首先我们画出这个程序的UML类图如下:

diagram-prototype


接下来根据类图,步骤一:编写Prototype抽象类

/**
*
* Prototype
*
*/

public abstract class Prototype implements Cloneable {

 public abstract Object copy() throws CloneNotSupportedException;

}


步骤二:编写继承Prototype的Beast、Warlord、Mage抽象类

/**
*
* Beast
*
*/

public abstract class Beast extends Prototype {

 @Override
 public abstract Beast copy() throws CloneNotSupportedException;

}
/**
*
* Mage
*
*/

public abstract class Mage extends Prototype {

 @Override
 public abstract Mage copy() throws CloneNotSupportedException;

}

/**
*
* Warlord
*
*/

public abstract class Warlord extends Prototype {

 @Override
 public abstract Warlord copy() throws CloneNotSupportedException;

}


步骤三:编写精灵族和兽人族具体子类,继承步骤二定义的抽象类

public class ElfBeast extends Beast {
 
 private String helpType;

 public ElfBeast(String helpType) {
   this.helpType = helpType;
 }

 public ElfBeast(ElfBeast elfBeast) {
   this.helpType = elfBeast.helpType;
 }

 @Override
 public Beast copy() throws CloneNotSupportedException {
   return new ElfBeast(this);
 }

 @Override
 public String toString() {
   return "Elven eagle helps in " + helpType;
 }

}

public class ElfMage extends Mage {

 
 private String helpType;
 
 public ElfMage(String helpType) {
   this.helpType = helpType;
 }

 public ElfMage(ElfMage elfMage) {
   this.helpType = elfMage.helpType;
 }

 @Override
 public ElfMage copy() throws CloneNotSupportedException {
   return new ElfMage(this);
 }

 @Override
 public String toString() {
   return "Elven mage helps in " + helpType;
 }

}

/**
*
* ElfWarlord
*
*/

public class ElfWarlord extends Warlord {

 private String helpType;
 
 public ElfWarlord(String helpType) {
   this.helpType = helpType;
 }

 public ElfWarlord(ElfWarlord elfWarlord) {
   this.helpType = elfWarlord.helpType;
 }

 @Override
 public ElfWarlord copy() throws CloneNotSupportedException {
   return new ElfWarlord(this);
 }

 @Override
 public String toString() {
   return "Elven warlord helps in " + helpType;
 }

}

public class OrcBeast extends Beast {
 
 private String weapon;

 public OrcBeast(String weapon) {
   this.weapon = weapon;
 }
 
 public OrcBeast(OrcBeast orcBeast) {
   this.weapon = orcBeast.weapon;
 }

 @Override
 public Beast copy() throws CloneNotSupportedException {
   return new OrcBeast(this);
 }

 @Override
 public String toString() {
   return "Orcish wolf attacks with " + weapon;
 }
 

}
//剩下的兽人OrcMage、OrcWarlord与上面的Elf类似编写即可...


步骤四:编写HeroFactory接口和它的实现类:

/**
*
* Interface for the factory class.
*
*/

public interface HeroFactory {

 Mage createMage();

 Warlord createWarlord();

 Beast createBeast();

}

/**
*
* Concrete factory class.
*
*/

public class HeroFactoryImpl implements HeroFactory {

 private Mage mage;
 private Warlord warlord;
 private Beast beast;

 /**
  * Constructor
  */

 public HeroFactoryImpl(Mage mage, Warlord warlord, Beast beast) {
   this.mage = mage;
   this.warlord = warlord;
   this.beast = beast;
 }

 /**
  * Create mage
  */

 public Mage createMage() {
   try {
     return mage.copy();
   } catch (CloneNotSupportedException e) {
     return null;
   }
 }

 /**
  * Create warlord
  */

 public Warlord createWarlord() {
   try {
     return warlord.copy();
   } catch (CloneNotSupportedException e) {
     return null;
   }
 }

 /**
  * Create beast
  */

 public Beast createBeast() {
   try {
     return beast.copy();
   } catch (CloneNotSupportedException e) {
     return null;
   }
 }

}


步骤五:编写App客户端类:

public class App {

 private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

 /**
  * Program entry point
  *
  * @param args command line args
  */

 public static void main(String[] args) {
   HeroFactory factory;
   Mage mage;
   Warlord warlord;
   Beast beast;

   factory = new HeroFactoryImpl(new ElfMage("cooking"), new ElfWarlord("cleaning"), new ElfBeast("protecting"));
   mage = factory.createMage();
   warlord = factory.createWarlord();
   beast = factory.createBeast();
   LOGGER.info(mage.toString());
   LOGGER.info(warlord.toString());
   LOGGER.info(beast.toString());

   factory = new HeroFactoryImpl(new OrcMage("axe"), new OrcWarlord("sword"), new OrcBeast("laser"));
   mage = factory.createMage();
   warlord = factory.createWarlord();
   beast = factory.createBeast();
   LOGGER.info(mage.toString());
   LOGGER.info(warlord.toString());
   LOGGER.info(beast.toString());
 }
}


最后运行App类,程序输出如下:

17:04:21.430 [main] INFO com.iluwatar.prototype.App - Elven mage helps in cooking
17:04:21.433 [main] INFO com.iluwatar.prototype.App - Elven warlord helps in cleaning
17:04:21.434 [main] INFO com.iluwatar.prototype.App - Elven eagle helps in protecting
17:04:21.434 [main] INFO com.iluwatar.prototype.App - Orcish mage attacks with axe
17:04:21.434 [main] INFO com.iluwatar.prototype.App - Orcish warlord attacks with sword
17:04:21.434 [main] INFO com.iluwatar.prototype.App - Orcish wolf attacks with laser


原型模式中主要有三个登场角色:
1、原型角色:定义用于复制现有实例来生成新实例的方法,上面的例子中Prototype类即是。
2、具体原型角色:实现用于复制现有实例来生成新实例的方法,上面的例子中ElfBeast、ElfMage、ElfWarlord、Orc...等都是。
3、使用者角色:维护一个注册表,并提供一个找出正确实例原型的方法。最后,提供一个获取新实例的方法,用来委托复制实例的方法生成新实例。上面的例子中HeroFactoryImpl即是。


至此设计模式中创建型模式我们已全部学习完毕。


大家可以试着去把工厂模式和今天学习的抽象工厂模式、单例模式、建造者模式、原型模式组合起来使用,每次组合1-2种设计模式来设计你的应用程序吧,自主设计出来你肯定会有意想不到的收获和体验的。



来自:小暴说

链接:https://www.jianshu.com/u/93e6c3dd7c46

(本文版权归原作者所有。转载文章仅为传播更多信息之目的,如有侵权请与我们联系,我们将及时处理。)


- End -

推荐阅读

搜索是神器Elasticsearch入门介绍

Spring Security权限框架理论与实战(一)

拜托!面试请不要再问我Spring Cloud底层原理

微服务之服务调用与安全控制

可能是一份最适合你的后端面试指南(部分内容前端同样适用)

可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章

支付系统

支付公司账务系统的那些事

从支付宝SDK的支付流程理解什么是公钥和私钥,什么是加密和数字签名

常用性能监控指南

线程池你真不来了解一下吗?


【推荐书籍】




      

        扫码购买



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存