ps:本文是转载文章,阅读原文可获取源码,文末有原文链接
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;
}
}
线程安,延迟加载,效率很低,不推荐使用,但是面试的时候可以说出来。