vlambda博客
学习文章列表

23种设计模式Java版第一篇


ps:这一篇是主要写工厂方法模式抽象工厂模式单例模式


我们在写程序的时候,为了后期能够更好的维护代码,都会用到设计模式,设计模式可以分为3大类,它们分别是创建型模式、结构型模式和行为型模式;其中创建型模式包含:工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式;结构型模式包含:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式和享元模式;行为型模式包含:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。设计模式是有规则的,总的来说是对扩展开放,对修改封闭,细致的规则遵循以下几种:


(1)单一职责原则,每个类应该实现单一的职责,特定的功能,不然的话把类拆分出来。


(2)里氏替换原则,一个类和另外一个类存在继承关系,父类出现的地方,子类可以出现;在程序中将一个父类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立;当父类被复用时,子类可以扩展父类的功能,但不能改变父类原有的功能。


(3)依赖倒转原则,面向接口编程,依赖于抽象而不是具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口进行交互;相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的框架比以细节的框架要稳定的多。在 Java 中,抽象指的是接口或者抽象类,细节就是具体的实现类。


(4)接口隔离原则,是指实现类中不应该存在实现一些他们不会使用的接口,应该把接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块,简单地说,就是使用多个专门的接口比使用单个接口要好的多。


(5)合成复用原则,它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。


(6)迪米特法则,一个类对自己依赖的类知道的越少越好,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。


说了这么多,下面说说工厂方法模式、抽象工厂模式和单例模式;


1、工厂方法模式


定义一个用于创建对象的公共接口,让子类实现这个接口并决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类;如果前面这个定义看不懂,先往下看,然后再回过头看想想就能理解多一些了。


工厂方法模式的角色如下所示:


(1)抽象工厂:提供了创建产品的规范,调用者通过它访问具体工厂的工厂方法来创建产品。


(2)具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。


(3)抽象产品:定义了产品的规范,描述了产品的主要特性和功能。


(4)具体产品:实现了抽象产品角色所定义接口的主要特性和功能。

下面我们用代码实现一下这个工厂方法模式;


ps:代码是在 eclipse 上写的


(1)抽象工厂角色,定义一个创建汽车的接口 CarFactory:


public interface CarFactory { public Car createCar();}


(2)抽象产品角色,定义一个汽车接口 Car:


public interface Car { public void run();}


(3)具体产品角色,定义2个类 AoDiCar 和 BiYaDiCar 并实现 Car 接口:


public class AoDiCar implements Car{
@Override public void run() { System.out.println("奥迪在跑"); }
}
public class BiYaDiCar implements Car{
@Override public void run() { System.out.println("比亚迪在跑"); }
}


(4)具体工厂角色,定义创建 AoDiCar 实例的类 AoDiCarFactory 并实现 CarFactory 接口:


public class AoDiCarFactory implements CarFactory{
@Override public Car createCar() { return new AoDiCar(); }
}


(5)具体工厂角色,定义创建 BiYaDiCar 实例的类 BiYaDiCarFactory 并实现 CarFactory 接口:


public class BiYaDiCarFactory implements CarFactory{
@Override public Car createCar() { return new BiYaDiCar(); }
}


(6)在程序入口实例化具体的车并调用汽车接口:


 AoDiCarFactory aoDiCarFactory = new AoDiCarFactory(); BiYaDiCarFactory biYaDiCarFactory = new BiYaDiCarFactory(); Car car1 = aoDiCarFactory.createCar(); Car car2 = biYaDiCarFactory.createCar(); car1.run(); car2.run();    


这里对上面工厂方法模式的定义分析一下,定义一个用于创建对象的公共接口就是创建了一个接口 CarFactory;让子类实现这个接口并决定实例化哪一个类其实就是 AoDiCarFactory 实现 CarFactory 接口并创建 AoDiCar 实例;工厂方法使一个类的实例化延迟到其子类其实就是不用实例化 Car 接口而是实例化它的子类 AoDiCar;根据开闭原则,每添加一个新的实现 Car 接口的子类,都会相应添加一个 CarFactory 的子类,例如添加了 BiYaDiCar,那么就会相应添加 BiYaDiCarFactory。


2、抽象工厂方法


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


(1)在上面 CarFactory、Car、AoDiCar、BiYaDiCar、AoDiCarFactory 和 BiYaDiCarFactory 这几个接口和类保持不变,把上面程序入口处的调用稍微修改一下:


 CarFactory factory1 = new AoDiCarFactory(); CarFactory factory2 = new BiYaDiCarFactory(); Car car1 = factory1.createCar(); Car car2 = factory2.createCar(); car1.run(); car2.run();


程序入口改成这样调用之后,工厂方法模式就升级为了抽象工厂模式;接口是负责创建一个相关对象的工厂就拿我们的例子来说就是 CarFactory 接口;不需要显式指定它们的类就好比我们的程序入口处代码  Car car1 = factory1.createCar(),而不是 new AoDiCarFactory().createCar();每个生成的工厂都能按照工厂模式提供对象就好比 Car car1 = factory1.createCar(),虽然 createCar() 返回的是 Car 接口,但 createCar() 实际创建的是 AoDiCar 对象,只不过向上转型了;无论是工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。


3、单例模式


单例模式在一个进程中确保某个类只有一个实例,单例类必须自己创建自己的唯一实例,单例类必须提供一个 public 方法给所有其他对象提供这一实例;单例模式有很多种写法,下面主要讲饿汉式单例模式、懒汉式单例模式、静态内部类单例模式、枚举型单例模式和双重检查锁单列模式。


3、1 饿汉式单例模式


饿汉式单例模式可分为4种情况写;


3、1、1 在静态方法中判断对象为空时创建


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


3、1、2 声明静态变量时创建


public class Singleton4{    private static Singleton4 instance2 = new Singleton4();    private Singleton4(){          }  
public static Singleton4 getInstance2() { return instance2;    }    }


3、1、3 声明静态常量时创建


public class Singleton4{    private static final Singleton4 instance3 = new Singleton4();    private Singleton4(){          } public static Singleton4 getInstance3() { return instance3;    }    }


3、1、4 静态代码块创建


public class Singleton4{private static Singleton4 instance4; static { instance4 = new Singleton4(); } private Singleton4(){    }  public static Singleton4 getInstance4() { return instance4; } }


第一种写法当出现线程并发的时候,线程没有进行同步,不能确保只有一个实例;后面三种在类装载的时候就完成实例化,实现了线程同步,但是使用静态final的实例对象或者使用静态代码块依旧不能解决在反序列化、反射、克隆时重新生成实例对象的问题。


3、2 懒汉式单例模式


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


只有使用的时候才会加载,懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个 Singleton5 实例,instacnce 对象还是空,这时候两个线程同时访问 getInstance 方法,因为对象还是空,所以两个线程同时通过了判断,开始执行实例化的操作。


3、3 静态内部类单例模式


public class Singleton2 { private Singleton2(){  } private static class SingletonInstance { private static final Singleton2 instance= new Singleton2(); } public static Singleton2 getInstance() { return SingletonInstance.instance; }}


实现了线程安全,又避免了同步带来的性能影响,延迟加载,效率高;静态内部类方式在 Singleton2 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton2 的实例化。


3、4 枚举型单例模式


public enum Singleton3 { INSTANCE;}


实现了线程安全,能防止反序列化、反射,克隆重新创建新的实例,是天然的单例模式。


3、5 双重检查锁单列模式


public class Singleton1 { private static Singleton1 instance = null; private Singleton1(){  } public static Singleton1 getInstance() { if (instance == null) { synchronized(Singleton1.class){ Singleton1 sc = instance; if (sc == null) { synchronized(Singleton1.class){ if (sc == null) sc = new Singleton1(); } instance = sc; } } } return instance; }}


线程安,延迟加载,效率很低,不推荐使用,但是面试的时候可以说出来。