本文最后更新于:1 分钟前
工厂模式 工厂模式的本质就是对获取对象过程的抽象。
简单工厂模式 简单工厂模式又称为静态工厂方法模型,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
对于产品种类相对较少的情况,考虑使用简单工厂模式可以很方便地创建所需产品:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public interface Fruit { public abstract void eat () ; }public class Apple implements Fruit { public void eat () { System.out.println("Eat Apple" ); } }public class Orange implements Fruit { public void eat () { System.out.println("Eat Orange" ); } }public class Factory { public static Fruit getInstance (String name) { if (name.equals("Apple" )){ return new Apple(); }else if (name.equals("Orange" )){ return new Orange(); } return null ; } }public class Client { public static void main (String[] a) { Fruit f=Factory.getInstance("Apple" ); if (f!=null ){ f.eat(); } } }
通过反射来获取创建对象的类会更加直观,此时增加产品也不需要每次都对工厂类进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public interface Fruit { public abstract void eat () ; }public class Apple implements Fruit { public void eat () { System.out.println("Eat Apple" ); } }public class Orange implements Fruit { public void eat () { System.out.println("Eat Orange" ); } }public class Factory { public static Fruit getInstance (String ClassName) { Fruit f=null ; try { f=(Fruit)Class.forName(ClassName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return f; } }public class Client { public static void main (String[] a) { Fruit f=Factory.getInstance("*.Apple" ); if (f!=null ){ f.eat(); } } }
但是方法参数是字符串,可控性有待提升,可以改为以下通过类方法newInstance直接创建对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Factory { public static Fruit getInstance (Class<? extends Fruit> clazz) { try { if (clazz != null ) { return clazz.newInstance(); } }catch (Exception e){ e.printStackTrace(); } return null ; } }public class Client { public static void main (String[] a) { Fruit f=Factory.getInstance(Apple.class); if (f!=null ){ f.eat(); } } }
在客户端 Client类 中通过switch方法创建相应类型的 Fruit 是完全没有问题的。为什么要单独分出来一个工厂类来进行对象的创建呢?
这是因为要考虑到代码的可修改性与可读性 。在不使用简单工厂的客户端代码中,如果增加一种类型的书,需要修改其中的switch语句,而其后的操作是不需要修改的。针对代码优化进行解耦,就要分离其中的变化部分以及不变的部分。当不使用简单工厂类进行创建对象时,需要从客户端中使用switch的代码中直接修改。如果主函数中使用switch的代码地方过多,需要全部修改,这就很容易漏掉某个地方,忘记修改,从而造成程序错误。简单工厂模式就是将这部分创建对象语句分离出来,由这个类来封装实例化对象的行为,修改时只需要修改类中的操作代码,使用时调用该类不需要考虑实例化对象的行为,使得后期代码维护升级更简单方便。这其实也是在对每处创建对象这一高耦合性的行为进行解耦。
优点 结构简单,调用方便。工厂和产品的指责区分明确。
缺点 工厂类很单一,同时负责所有产品的创建,当产品基数过多时,工厂类会非常臃肿,违背高聚合原则。
工厂方法模式 工厂方法模式指定义一个创建对象的接口,但由实现这个接口的类来决定是梨花哪个类 。
工厂方法模式适用于以下场景:
创建对象需要大量重复的代码
客户端不依赖产品类实例如何被创建、实现等细节
一个类通过其子类来指定创建哪个对象
以下是工厂模式的通用写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public interface Fruit { void eat () ; }public class Apple implements Fruit { @Override public void eat () { System.out.println("Eat Apple" ); } }public class Orange implements Fruit { @Override public void eat () { System.out.println("Eat Orange" ); } }public interface Factory { Fruit getInstance () ; }public class AppleFactory implements Factory { @Override public Fruit getInstance () { return new Apple(); } }public class OrangeFactory implements Factory { @Override public Fruit getInstance () { return new Orange(); } }public class Client { public static void main (String[] args) { Factory factoryApple = new AppleFactory(); factoryApple.getInstance().eat(); Factory factoryOrange = new OrangeFactory(); factoryOrange.getInstance().eat(); } }
优点
灵活性强,对于新产品的创建,只需要多写一个相应的工厂类
是典型的解耦框架。高层模块只需要知道产品的抽象类,无需关心其他实现类(满足迪米特法则、依赖倒置原则和里氏替换原则)
缺点
类的个数容易过多(指每增加一种产品就会多一个产品相关的工厂),增加复杂度
增加了系统的抽象性和理解难度
抽象产品只能生产一种产品(这个可以通过抽象工厂解决)
抽象工厂模式 抽象工厂模式指提供一个创建一系列相关或依赖对象的接口,无需指定他们具体的类。(意思是客户端不必指定产品的具体类型,创建多个产品族中的产品)
产品等级结构:产品等级结构就是产品的继承结构。 比如一个抽象类是水果,它的子类有苹果和橙子。抽象水果和具体水果就构成了一个产品等级结构——抽象水果是父类,具体水果是其子类。
产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品 。比如苹果工厂生产的苹果和苹果汁,或者橙子工厂生产的橙子和橙汁。
抽象工厂模式的通用写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public interface Fruit { void eat () ; }public class Apple implements Fruit { @Override public void eat () { System.out.println("Eat Apple" ); } }public class Orange implements Fruit { @Override public void eat () { System.out.println("Eat Orange" ); } }public interface FruitJuice { void drink () ; }public class AppleJuice implements FruitJuice { @Override public void drink () { System.out.println("Drink AppleJuice" ); } }public class OrangeJuice implements FruitJuice { @Override public void drink () { System.out.println("Drink OrangeJuice" ); } }public interface Factory { Fruit getFruit () ; FruitJuice getFruitJuice () ; }public class AppleFactory implements Factory { @Override public Fruit getFruit () { return new Apple(); } @Override public FruitJuice getFruitJuice () { return new AppleJuice(); } }public class OrangeFactory implements Factory { @Override public Fruit getFruit () { return new Orange(); } @Override public FruitJuice getFruitJuice () { return new OrangeJuice(); } }public class Client { public static void main (String[] args) { Factory appleFactory = new AppleFactory(); Factory orangeFactory = new OrangeFactory(); appleFactory.getFruit().eat(); appleFactory.getFruitJuice().drink(); orangeFactory.getFruit().eat(); orangeFactory.getFruitJuice().drink(); } }
优点
如果需要增加产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品族
增加新产品族时,只需实现一个新的具体工厂就行,不需要对已有代码进行修改
缺点
规定了所有可能被创建的产品集合,产品族中扩展新的产品就比较困难,需要修改抽象工厂的接口
增加了系统的抽象性和理解难度
单例模式 饿汉式 饿汉式单例写法在类加载的时候立即初始化,并且创建单例对象。它绝对线程安全。
1 2 3 4 5 6 7 8 9 10 public class Singleton { private static final Singleton instance = new Singleton(); private Singleton () {} public static Singleton getInstance () { return instance; } }
饿汉式单例写法适用于单例对象较少的情况。这样写可以保证绝对线程安全,执行效率比较高。但是它的缺点也很明显,就是所有对象类在加载的时候就实例化。
懒汉式(线程不安全) 为了解决饿汉式单例写法可能带来的内存浪费问题,于是出现了懒汉式单例的写法。懒汉式单例写法的特点是单例对象在被使用时才会初始化。
1 2 3 4 5 6 7 8 9 10 public class LazySimpleSingleton { private static LazySimpleSingleton instance; private LazySimpleSingleton () {} public static LazySimpleSingleton getInstance () { if (instance == null ){ instance = new LazySimpleSingleton(); } return instance; } }
懒汉式(线程安全) 1 2 3 4 5 6 7 8 9 10 11 public class Singleton { private static Singleton instance; private Singleton () { } public static synchronized Singleton getInstance () { if (instance == null ) { instance = new Singleton(); } return instance; } }
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。
双重检查模式(DCL) 这个就非常重要了,面试的时候考的最多的就是这个单例模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton () {} public static LazyDoubleCheckSingleton getInstance () { if (instance == null ) { synchronized (LazyDoubleCheckSingleton.class) { if (instance == null ) { instance = new LazyDoubleCheckSingleton(); } } } return instance; } }
当第一个线程调用getInstance()方法时,第二个线程也可以调用。当第一个线程执行到synchronized时会上锁,第二个线程就会变成 MONITOR 状态,出现阻塞。此时,阻塞并不是基于整个 LazyDoubleCheckSingleton 类的阻塞,而是在 getInstance() 方法内部的阻塞,只要逻辑不太复杂,对于调用者而言感觉不到。
枚举单例模式 上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class HungrySingleton implements Serializable { private static final HungrySingleton instance = new HungrySingleton(); private HungrySingleton () {} public static HungrySingleton getInstance () { return instance; } }public class EnumSingletonTest { public static void main (String[] args) { try { HungrySingleton instance1 = null ; HungrySingleton instance2 = HungrySingleton.getInstance(); FileOutputStream fos = new FileOutputStream("HungrySingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("HungrySingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); instance1 = (HungrySingleton) ois.readObject(); ois.close(); System.out.println("before singleton: " + instance2); System.out.println("after singleton: " +instance1); System.out.println("compare two singletons: " +instance2.equals(instance1)); }catch (Exception e) { e.printStackTrace(); } } }
运行结果为:
1 2 3 before singleton: javaBase.DesignModel.SigletonModel.HungrySingleton@12a3a380 after singleton: javaBase.DesignModel.SigletonModel.HungrySingleton@27d6c5e0 compare two singletons: false
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,所以我们可以通过枚举类来实现单例模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public enum EnumSingleton { INSTANCE; private Object data; public Object getData () { return data; } public void setData (Object data) { this .data = data; } public static EnumSingleton getInstance () { return INSTANCE; } }public class EnumSingletonTest { public static void main (String[] args) { try { EnumSingleton instance1 = null ; EnumSingleton instance2 = EnumSingleton.getInstance(); instance2.setData(new Object()); FileOutputStream fos = new FileOutputStream("EnumSingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); instance1 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println("before singleton: " + instance2.getData()); System.out.println("after singleton: " +instance1.getData()); System.out.println("compare two singletons: " +instance2.getData().equals(instance1.getData())); }catch (Exception e) { e.printStackTrace(); } } }
运行结果为:
1 2 3 before singleton: java.lang.Object@4f3f5b24 after singleton: java.lang.Object@4f3f5b24 compare two singletons: true
优点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
缺点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
原型模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public interface Fruit <T > { T clone () ; }public class Apple implements Fruit <Apple > { private String name; public Apple (String name) { this .name = name; } @Override public Apple clone () { return new Apple(this .name); } @Override public String toString () { return "Apple{" + "name = " + this .name + "}" ; } }public class Orange implements Fruit <Orange > { private String name; public Orange (String name) { this .name = name; } @Override public Orange clone () { return new Orange(this .name); } @Override public String toString () { return "Orange{" + "name = " + this .name + "}" ; } }public class Client { public static void main (String[] args) { Apple applePrototype = new Apple("apple" ); System.out.println(applePrototype.toString()); Apple apple1 = applePrototype.clone(); System.out.println(apple1.toString()); System.out.println(applePrototype.equals(apple1)); } }
优点
java自带的原型模式是基于内存二进制流的复制,性能上比直接new一个对象更优。
可以使用深克隆方式来保存对象的状态:使用原型模式将对象复制一份,并将其状态保存起来,这种操作简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可以辅助实现撤销操作。
缺点
需要为每一个类都配置一个clone方法
clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则
实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来就会很麻烦。
建造者模式 建造者模式将一个复杂对象的构建过程和它的表示进行分离,使得同样的构建过程可以创建不同的表示。
构建代表创建一个对象,表示代表对象的行为、方法。
对应到代码其实就是用接口规定行为,然后由具体的实现类进行构建。
如果把我们买车的时候定义的车辆配置(低配、标配、高配,还有可能增配、减配)使用建造者模式来表现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @Data public class Car { private String brand; private String type; private String engine; private int seat; @Override public String toString () { return "Product - " + "brand: " + brand + " type: " + type + " engine: " + engine + " seat: " + seat; } }public interface Builder { Car builder () ; }public class CarBuilder implements Builder { private Car product = new Car(); @Override public Car builder () { return product; } public CarBuilder addBrand (String brand) { product.setBrand(brand); return this ; } public CarBuilder addType (String type) { product.setType(type); return this ; } public CarBuilder addEngine (String engine) { product.setEngine(engine); return this ; } public CarBuilder addSeat (int seat) { product.setSeat(seat); return this ; } }public class Client { public static void main (String[] args) { Builder builder = new CarBuilder() .addBrand("Benz" ) .addType("AMG" ) .addEngine("V8" ) .addSeat(2 ); System.out.println(builder.builder()); } }
建造者模式 vs 工厂模式
建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
建造者模式能创建复杂的对象,由各种复杂的部件组成;工厂模式创建出来的对象都一样。
它们关注的重点不一样。工厂模式只要把对象创建出来就行了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件构成。
建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。
优点
封装性好,构建和表示分离
可拓展性比较好,建造类之间独立,在一定程度上解耦
便于控制细节,建造者可以对创建过程进行逐步细化,而不对其他模块产生影响
缺点
需要多创建一个 Builder 对象 (建造者对象)
如果产品类内部发生变化,则建造者也要同步修改,后期维护成本较高