工厂方法
采用OO(面向对象)进行程序开发,类与类之间避免不了通过创建对方的对象进行联系,这种设计在带来开发上的便捷时也让不同模块的类有着藕断丝连的关系,即模块间的耦合度高。如图(来自Java软件体系结构设计模式标准指南):
在类层次结构中,SubClass_2和SubClass_1都继承于ParentClass。而在可户中App_object需要类层次结构提供的一些服务。但问题是,客户有着很多不确定性,即它有可能要用到SubClass_2或者SubClass_1。那么,采用这种模式进行程序设计时,你可能需要加一些条件判断(当符合XXXX时选择SubClass_2,当XXX时选择SubClass_1),用句牛逼点的话来说,你每次都要实施类选择标准。
由于客户对象(要使用类层次结构所提供的服务)都需要实现类选择标准,这会在客户对象与服务提供方(类层次结构)之间产生高度耦合,表现在如下方面:
(1)当类选择标准发生变化时,每个客户对象都必须进行相应的更改。(2)由于类选择标准需要考虑可能会影响选择过程的所有因素,因此客户对象的实现可能会包含不适当的条件陈述。(3)如果类层次结构中的不同类需要以不同的方式来创建,客户对象的实现可能就会变得更复杂。(4)它需要客户对象完全了解服务提供方(类层次结构)中每个类的存在性及其所提供的功能。
于是,工厂方法建议采用如下方式进行设计:
通过上图所示,我们可以发现工厂方法中,父类(或者接口)定义创建对象的公共的接口,而具体创建什么样的对象是在它的子类(若是接口,则应该是实施者)中实现的。客户端不再直接依赖类层次结构,而是依赖抽象,这种设计方式无疑更加可靠,无论是可拓展性,维护性都要远胜于前者。
下面是一个简单的实例:
package top.fang.factory;
/**
* 计划卖花,于是事先定义一个花的种类的接口
*/
public interface Flower {
void getFlowerType();
}
package top.fang.factory;
public class RoseImpl implements Flower {
@Override
public void getFlowerType() {
System.out.println("类名中文是玫瑰花实现类");
}
}
package top.fang.factory;
public class TulipImpl implements Flower {
@Override
public void getFlowerType() {
System.out.println("类名中文是郁金香实现类");
}
}
这上面三个类(接口)就相当于UML图中的类层次结构。接下来是定义一个工厂的总的接口,然后分别实现造玫瑰花的工厂和郁金花的工厂。
package top.fang.factory;
/**
* 这是一个造花工厂的公共接口
*/
public interface IFlowerFactory {
Flower getFlower();
}
package top.fang.factory;
/**
* 这个工厂用来生产玫瑰花
*/
public class IRoseFactoryImpl implements IFlowerFactory {
@Override
public Flower getFlower() {
return new RoseImpl();
}
}
package top.fang.factory;
/***
* 这个工厂用来生产郁金香
*/
public class ITulipFactoryImpl implements IFlowerFactory {
@Override
public Flower getFlower() {
return new TulipImpl();
}
}
最后是模拟客户端对象进行造花:
package top.fang.factory;
public class MainApp {
public static void main(String[] args) {
IFlowerFactory test;
test = new IRoseFactoryImpl();
test.getFlower().getFlowerType();
test = new ITulipFactoryImpl();
test.getFlower().getFlowerType();
}
}
打印
类名中文是玫瑰花实现类
类名中文是郁金香实现类
从上图我们可以看到,采用工厂方法,将客户对象和提供服务的业务隔离开了,客户对象不再需要关心你的业务,当你需要进行业务扩展时,提供相应的工厂实现就可以了,这也是六大原则中,开闭原则(对修改封闭,对拓展开放)和单一职责原则(每个工厂具有单一的职责,而不是混杂多个任务)的体现。
单例模式
单例模式的应用也是非常 常见的,特别实在多线程的开发中,其应用更为广泛。它的目的在于确保某个类只有一个实例帮助客户对象以一致的方式访问单个实例,之所需以要单例,无非就是确保在多线程的环境下,数据不会产生脏读即线程的安全性。另外,在Spring中,单例也是其默认的实现方式。
下面举例描述几种Java的单例模式的写法:
package top.fang.signal;
public class SingalFile {
//直接申明为静态对象,它在类进行加载时就跟着一起加载,外部访问时共享这一变量
private final static SingalFile singalFile = new SingalFile();
//构造方法私有化,这样外界不能通过new进行对象的创建
private SingalFile(){
}
public static SingalFile getSingalFile(){
return singalFile;
}
}
这种写法也称为懒汉式写法,它虽然简单,但缺点也很明显,即类在加载时,对象也跟着加载,如果大部分时间内,这个对象几乎处于休眠状态,那么将造成空间资源的浪费。
package top.fang.signal;
public class SingalFile_ {
//这里只申明了引用,并未进行new操作
private static SingalFile_ singalFile_;
//构造方法私有化,这样外界不能通过new进行对象的创建
private SingalFile_(){
}
//加同步锁,确保多线程环境下的线程安全性
public static synchronized SingalFile_ getSingalFile(){
if(singalFile_==null){
singalFile_ = new SingalFile_();
}
return singalFile_;
}
}
这里之所以加同步锁,是因为如果不加锁,在多线程环境下,当一个进程来到if(XXX)语句时,还没来得及往下执行,就有另一进程进入,这时,将产生多个实例。但是这种写法效率很低,每次调用getSingalFile()方法都需要进行同步,其实,最好的方法是在第一次进行实例化时可以同步,后面的话直接return。
在多线程设计时,我们在保证线程安全的前提下加锁的粒度要尽可能的小,于是可以采用如下方式:
package top.fang.signal;
public class SingalFileDoubleCheck {
//这里只申明了引用,并未进行new操作,这里要还要申明volatile
private static volatile SingalFileDoubleCheck singalFile;
//构造方法私有化,这样外界不能通过new进行对象的创建
private SingalFileDoubleCheck(){
}
//加同步锁,但锁的粒度变小了,不再锁住整个方法
public static SingalFileDoubleCheck getSingalFile(){
if(singalFile==null){
synchronized(SingalFileDoubleCheck.class){
//引入双重检查,确保线程安全
if(singalFile == null){
singalFile= new SingalFileDoubleCheck();
}
}
}
return singalFile;
}
}
}
}
这里引入解决线程安全比较常用的方法,即双重检查。如果不加里面的 if(singalFile == null)进行判断的话,还是会发生跟上面一样的错误,两个线程进入外面的 if(singalFile==null),但 synchronized只是阻塞一个进程,在另一进程运行完释放锁后,还是能够继续执行。这种写法效率较高,在确保线程安全的同时还能延迟加载。
更新:在这篇文章中,作者讲述了为什么需要在修饰对象时采用volatile ,大家可以看看。
package top.fang.signal;
public class SingalFileByInClass {
//构造方法私有化,这样外界不能通过new进行对象的创建
private SingalFileByInClass(){
}
//申明静态内部类
private static class InClass{
private final static SingalFileByInClass singalFile = new SingalFileByInClass();
}
public SingalFileByInClass getSingalFile(){
return InClass.singalFile;
}
}
这里采用了静态内部类来设计单例模式,它和第一种方式类似,只不过它不像第一种在类装载时,就实例化了对象。这里在内部类被装载时并不会立即实例化,而是在需要时才会进行,具体表现就是有人调用了getSingalFile()方法。
package top.fang.signal;
public enum SingalFileEnum {
singalFile;
}
这种方式采用枚举类进行实现的,它充分了利用了枚举类的特性,实现单例的同时也确保了线程安全,为什么枚举类可以实现,大家可以去看看枚举类的实现原理,或者将枚举类进行反编译即可得到它的源码。